Files

6276 lines
378 KiB
JSON
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{
"definitions": {
"Assignment": "2b8f7430f4db7252b25e9db231c6684f87ca2de21e2933d107c9773888ea661f",
"Base": "c1cbd280eee0b89102476fb3994dacd59844e10d24e86fda68c649fd3c1b1c59",
"MonthBlock": "287cce589bc05e5da096fabe126ddfffbf693c4ce32829aa97f9f3e6d6c0df84",
"MonthlyPattern": "00e73673967d1aa86d664c0c0b89ff55156638c63e544a16228574a0c4ef9c78",
"NotificationLog": "8b7291e357576d5ca0ca6d5bafe340bdbdfc5bcf541fcb10097be5a0367e4507",
"RotationMember": "43203a53701c7433992f94be9288c2dfaecd4ca6ab58b6b2577330068d61a3ac",
"Schedule": "50a3afabda23f150960b9a5372eeac29b49646243cffc0a1c24fb4e691b806e9",
"Settings": "9ec5987f4c5d99c6cc65597be1d95b4aa8d2bbb7ee8d6445690dfe41184f4957",
"SwapRequest": "a699dcbc2bfcc415d993ead48c028663ab06e8eb54bc77516235f174a51589c5",
"Team": "989de12ee34d86bd747d257441e2a2fb3718e0053790ed95c146df5cc1ab0924",
"User": "941134ed5efbd2464e8e8918a0138ee2bdaa228fa5e1f988ef0e3436075334d6",
"_accepting_team": "d65db64047442d79f4654985e0cf1aa15614570e7586eedda1f81376ca75a3db",
"_active_request": "df41ac832e5720158fbbb8b176979b3e50697dcdcef6ea123a7b7d28df153cc0",
"_active_schedules": "d0fca80985d60f88ccc47c8b4ea603761812c35fc3bcc8f80253c387c0775eea",
"_admin_teams": "841c05fa8ed4eef1426509763d55e950fd665f9f477fd97c95683b9439707699",
"_all_schedules": "8fe6504cecab40a403132f13031277d61e276b337b5fee5b1fe686831dde9bf8",
"_apply_pattern_forward": "50ad2b7df7843ec36efc3c61d4a7252d63d4121c3944d5e735f2160a421676f5",
"_apply_pattern_to_open_rows": "24076e69176477dd74da34c7f69a1342bef292e5ad0f42732674edda5b98f0d3",
"_apply_rotation_to_rows": "a2a02c003b9b9e4fc64023898bcc98c5aa5173be6fa8b86f8c023e2a9b5e35c6",
"_assign_day_url": "351850c9e71cc95cc25579bf3a7e145d284c5883ab749b8da49cbd21aa59cff1",
"_assign_user_to_teams": "cf2c8ab64e7bdccf149f7545c09a889ccd9d0383aa8a79fc3d0412dbba62edf7",
"_calendar_token": "d33a7dbd8af12b55164b2c544c8f3a4c7527b359136d8937592a9d79578c0076",
"_check_csrf": "96b5b121cc98f9043f0fa41b4bb19b3bf700ad892702c24ea8b3bce1f0782121",
"_chosen_user": "baa8d0dcc33d5cde5b0c9f236ea483508d11763f462033680703894d845d2f44",
"_clear_assignments_from": "573dbe74cb5555869db83a92968b5280d37cf9f0e5d72194b81d7a866c3e9403",
"_columns": "9e9ac42b8d5c8d5f3f6b81d9222df02b18f1c9670e3d064f1b7ef30f0af2fb3a",
"_complete_swap": "507ee15bb500ce0f40f77e90b5f6c02c4e94b1d4c95ce5366a76994f923eca59",
"_context": "baa2d501f6e418152cbebc56f07dfe55eb35154829416a15ff7fa79ba2a3e467",
"_create_swap": "64cb526a882e09a01945b960f87de7724cae7322206a0a5a65cafa472c352b17",
"_csrf": "4be30b4c04bd4246f08fed3782ff0155eea4eb691032b6c0cd6e98211e9a7a7b",
"_current_user": "42bb53fff71dff01cf1455a85817a8975178bfb9faab6221e11eef56deb7df11",
"_default_team": "6730fd6dbed43670b09335d507eef9e3ebfd0df7038912cfa28e7d926b97abb3",
"_escape": "0e70e07ca6156c79fed26b865c204f7022e83e5641a32b2d9a30b0ef76dca618",
"_event": "597dd397cd3369ce32e3b439b4d3143c09cbe97cd3ebebead88424087fec5b90",
"_flash": "f319e7a8b69c1b1654887f3b7e7ac724a8286e9d6f8c8def05e49be3b1936b42",
"_fold": "2082572e5cf97c4d236edd0f59b0729b4a169fceed9dc63b61b9bbf356e71ac7",
"_generate_rotation_range": "5a3c32c3e8a0066436fb34f55a8345a0ad36633462bfc2c8c00af07f99938068",
"_get_schedule": "c658002d06bcba95b148651477f09a125aaa6982f6e260d9f0d67aac04f64728",
"_host": "0587b3d3b29c9065e9d64bcf7559e80e2695dbc89929ae0cb28820297de8f9bf",
"_layout": "4c670e9cce524c679deabb4cbd1a17f89e431c2f059442a6c20e75a1573d73e9",
"_loses_last_team": "d23d67a6a08920b6eb31f7671f90eb0c1c2078f139182a8e17452ca035cda286",
"_month_assignments": "922fa4b0c0df6a77641168d2d2dbab5437977524c12958d5bc89f7289e4119d5",
"_month_nav": "0a5a7f4fe43eefab3b1b4a86e71f2b87251371d571786cec7729f40976d0db6b",
"_new_user": "6998933630f0044d5a9254ce1dc9ae8ac11dcb010c8ada965158ff114d82acf5",
"_next_dates": "f103e97d6350e7db09aa014c4cbcd4fcfcaadcf6b7c8dfe604eed07447a67740",
"_notify_assignment_changes": "29c60bf23d25aa77aaf770388f0a6fb8558216c8737182970b7331fb12ce8e33",
"_open_swap_requests": "f095afa9428aed7f1af9d524fd05b118a99518b919a9880089c1a9cf6b9b0e9b",
"_parse_month": "f364954e7acf36646e60bba89492d98d9a0b9474c1321c862db4afd369a44ed5",
"_parse_time": "3a2c7134356e971c5f5c30f60df74ea6f2de74ae7eb097efcc49e2b599c33dc0",
"_pattern_map": "8b94d9b9c5c57adfe91ae5287abfafaa260b888dae77b3d76f3e6d3d98360d6a",
"_redirect": "f78bec5bb9fabde6e3b768a3a6d4383785db3d42a8e355517f52f8d00af1240a",
"_reject_swap": "e9c09521a9b3dca0950bf61ee08dc5edefa9112619d37ecd0b8df1db5e18cd57",
"_rejectable": "6c86715704c166a334736e0cb79c5df74cddb39d42a91ee84b82d5e6b1ebaddd",
"_render_admin_schedules": "75c2260c9410ef8dec1fe023e7bca6ef6c719d2357eb90a9e05065d672f85bbc",
"_require_admin": "5b64c94b267b2e073e5346314b697e0159719f926f86d6baf9ba25c852b14098",
"_require_removable": "789c50862c3c058de4f88cf97e84a34de77d5bd846ad5982761490e58988e6c5",
"_require_user": "9b602a8921681453dd541d63af2fb5c1e59a0571398ae867e6f11037341dddcd",
"_rotation_occurrence_index": "e0a5e24763920abae350ffb3090f7f10b0d1034fa1ee618c646e6a9f9c2bcd4c",
"_safe_back": "99eae35a7eaea120c822dfa8a6c73dd3628a177ea896b34adccbbaca94e92fa3",
"_save_assignments": "6fbc815ff286c0500cec0860939aedea5876d5acffa4255f3c870d8740371685",
"_schedule_card": "e9f09d2c6a0695b219019b9ecdd4bebb3df9b4596020a5a8cd7e9b410392644d",
"_schedule_groups": "bdc5ede2e7cde69e09a1ac679b977cbf94414e0c56be16c92ad196e00de15ba5",
"_schedule_page": "3c6cf42c5c0182d017ddff4c48e54c32292a6074cded9a8e923179eaf9bf1d26",
"_schedule_team_ids": "9c8759326172090b172c581384ce50c0ec12a2f2e3f252bbd24d587d66ed193f",
"_schedule_teams": "0eb69900958f5cd1e2d2790a22f66b499585c5e1b1a6e9f37e1becd26a6005c8",
"_schedule_teams_map": "9b6434331fb2a6fc0603ddf2c4c049fc506710a23609890af8f2c9df384c0726",
"_set_assignee": "6661e42e0b623187b9a94f1967bf02e50471ec1f6ff4f9a402170dfdc1dada4f",
"_shared_calendar_url": "8cdc4d40e4286372570ee2ecd0d263ae31a6252700d3815ba1665f714c0885a2",
"_stamp": "6b94fc759e61245dc15fe0ad39892899cc4f4222317f36fd5f394d35424c1e26",
"_swap_ownership_holds": "3f73630e67f92ab23f7c6db4d02c4e83240b25b645fcf3f8ad26b0506249b9c6",
"_swap_partner": "0faa38b24ae828587bab0da613b14f64046a743f347d65e07e45bb3d73b04ec4",
"_swap_side": "08c5a56b48cf3a65e526b84c07efc890e902270d2945014cefc4fcf11c870efe",
"_team_removal_blocker": "54371319c371902d06dff4f21fc22c95470af2b44f222fb9c07a1e26a722c351",
"_team_removal_impact": "2881010e02c6b9cc55103d2f1a104222f113652f67c864e97a8dd2fbfcbff9a5",
"_timezone_block": "b653b1b2af407bca96cea3a5399566c57d0645239f9ae89a2307aa5300119abd",
"_usable_admin_users": "699592d85a158c406d747f042f58d35668cfa70f71800ed8ab78066c371f92ee",
"_user_label": "83c65b6ef7163866c30c4d0178143dc3723e00db7bfed515f73515878469d856",
"_user_removal_blocker": "66d9034be0907e2d142faa2b4cbc116e2f1a366fbaa44881ecd1a4f3781374b0",
"_user_team_ids": "d643445a1676ea45ae29f3c4272e60f297fc7cab934eb904fc30d93174a4e10f",
"_user_teams": "cf4a74bc2fe491f76dfd99ad943398b90cb0490b3853d9a9759303759159d72b",
"_users_by_id": "027ccd114b6404a77b73d99276a18758ff92a44f68ed7a0b5e4afa4fcfe97aca",
"accept_swap": "84d54fe1b19966de00e31d7db4ea85910053b6887ae1a31c89f4d3ccb125cb71",
"account_page": "eed96aa30d46aa92c42356a961cef63e3847e2ecc5d05740510dd3d413018a6c",
"account_password": "4545d86d3e2a1a2d2dbd266167b873900ffb82d981fc5d5d36d58faa5dc0ed6e",
"admin_apply_pattern": "a71528fe2456fe20201e97df6b936ca0a7d7edc59a5950b30ca8db57168d5d62",
"admin_apply_rotation": "d5c45d851ba1929c2fa637101f13bcb972c59a2c213a0a51f7e12721599b5275",
"admin_assign_day": "f14c110ae7654bcdcda09f5a2462bbd96fced63f216ff4b6b13d97370ed7d3b6",
"admin_assign_team": "9b76661f576260bb5325c242dd21c6c543756327b4d8416c2f8c0471adb44b88",
"admin_assignment_regenerate": "41082e4adcf257240ef5520268f8c611f2836fdddd6e5908564331c894a639bd",
"admin_clear_from": "ed1cc3f147a1318c8594edbce9785b13206ca5dbd8c8c3d560e07418dc2cf646",
"admin_create_team": "b33dd7f9dac0a1aa9557d20b4eee6ffee38ab2cb49ed2e7f12d63ecb6ebc8d51",
"admin_create_user": "2b08d3f2cb2ece36612585d1096b0c81e2b35d8bb39a1985cfaac8c03f2b14bb",
"admin_month": "eb75e48f5f33f84ade39b9f6a117d3b43c7fd340938d0618c33c418d1ae986d0",
"admin_notifications": "c9c76ee7f89a0c0d233dd29390bdeacf0b4b57bfae4a996c6aa0044f923ce886",
"admin_reject_swap": "ee3da8617187b8594a9f4e8d700c2dd2fe3fb71c7f4e64a3151acacd1838c1c2",
"admin_rotation_add": "6d688b41898fc4be7a6823b65fe51964e97971dcf9381ee5104d02f0a79b48a8",
"admin_rotation_move": "c140ad14df549d198b96e4f667b419eb9c4679d863ef4f8f5f8319310dff2924",
"admin_rotation_remove": "83ee03662a0e1e4eb0138e8312f347c694e066dff2959953f9395b4ffb53586d",
"admin_schedule": "8749d7d1e1964e73d91da3594e333d75bf7aa1e65beb44b001e4e3331c8b5e63",
"admin_schedule_assign_swap": "36c2cb1e871bcecd42fab64807d967f5e7c906fbb93763eda086b7a636315f15",
"admin_schedule_assignments": "f7d833d769a4a7967da81c25267080be799414b1d26459dbae8bcf5e37cb970a",
"admin_schedule_pattern": "fc11cb71db195140b36f33a6115ef63f0f9251c3b8b581c5988e1d4955c3a41e",
"admin_schedule_settings": "aa33b00fcebeebe288ea69c480f254c0e4fd92fe60cd2f0a366370df26cac576",
"admin_schedules": "456005bcb0cce35f16dc22d77cf9cab459814697da0fa1b71ed80b06abe6d571",
"admin_team_delete": "8202aeaaa7c478a874f6d5431b8e08c2c876b1adcb27c90c1b805d57de09f974",
"admin_team_delete_page": "2eb324bf154ddcb0efbd732637a9aa16da7694e0d2354bc9869463d0b2bd9fa2",
"admin_teams": "8a768f6b5e693318d37ea96e6f2f9ae05cbf0c87a785908ba79bf1338feda490",
"admin_update_team": "a214c0ce72227a0b2865a0a8a9dad73e6703ac4bc8f675903b01a4d0d436faab",
"admin_update_user": "320453b7a8a37ce03d01763b3192037df8b8149da4a9b4ab911d8a4a1dd8aea2",
"admin_user_delete": "b35988c6b835b8a88d67ec87ae9fe76ba80badc4057a975fd383d6dae7573cb0",
"admin_users": "ab6dfe526dce24d96c369e0602ac8207ef5af570d9b2765ce8b3fcb6b285e1e4",
"alembic_config": "2952ea6c5985853514aae3e8a7b74f0f1d0d49b56b45ca0c82874d7673ae574f",
"approve_swap": "fe63883c1247cdec87036e1093c8675a8dbbdf55a98cf891de8fd73fca23fe03",
"assignments_by_date": "3cf3707a6fd0431806e068c7513781e3f36b77a6c38bc261ba8c5817d9ba446f",
"backup_database": "ece880319f6cec2023f52c7c6cc6f1ba56df575b9263abb8773efa2f152c7f46",
"build_feed": "c161e5ec4587a8c188242cd891be89cec2cd760faf6f93f8b2a3bf815ae7c3c9",
"build_schedule_pdf": "99ec4859f0f8332e38a4ef974b1a24d873f8f227320a9c151924cf7f616edc10",
"calendar_feed": "a9092ad9e9c3a713f8b12425e708c183723ac1704f57b1828583b11c77ad6f03",
"calendar_page": "5af6e6706128abfda036e1f40f5e0cc2196870f58a12b558aad0e298ff0e8da0",
"cancel_swap": "f70e2ad1ea81e6e3a3de61341d2141a31b9ec2a5d7aa92060e1504b8f548b122",
"create_swap": "8d4046a547b2c977fe0d7a9e1d6befd032a4fefbf8a7a947cf1cd4f0bf46af4d",
"create_swap_for_chosen_shift": "e1d61736a3abd5f6781db7ff27dcc10dd150e608e3118f22b9108acbb1155a10",
"current_revision": "5cfbec0632f55ac47e35defd403e4fc376c1dfe41d3d7e2257cc979ce30df1e6",
"dashboard": "2fe7dc610a557407ba095415126ecd02410041b061188fe680608c6798194888",
"database_file": "3623fea87f7c0a7c7341c83acaab3ae265ec915f85e128e3cae61529c6a439bc",
"day_long": "6b5c7e11721516feb42c0e6c826ec8a86e0d523face81638c6012a3bb8217c1a",
"day_numeric": "70aff7071c0ecc5467a92e65a3a03b0c0a22c8dade6f38d8ef258369c95fca6d",
"day_numeric_long": "43e31e8fb96375cd431d46d02d624ab267890be75ed72d0fe3a10a0f5b5d2aa4",
"day_short": "4e070187de377679c1a84343eb19379c6e11612aff9dcd9af6962dc376e4d9a3",
"db_shell": "a035d7c3181bfba100122efe7d1b7c5db7560442179e82d30dc8dba3c9d101fe",
"download_schedule_pdf": "6664e4a367234ccaaef21ac51c505bd7269df98672cb3e272a0f6c7de0f00d58",
"ensure_initial_data": "50e3dccd6cf986881214f712f207a8e3286e438d87b310eec3b47013865ca625",
"ensure_month_assignments": "24a5fed180353adee1205d0f5fbd2eb82faa71f79d8a0c9884dba4ebe1010ab8",
"event_type_label": "1cbd11dd0645812c165723f4ac12330e5b3a79e21d55b894951ae091693dcca3",
"export_schedule": "55cf4b4cedf517ca639948c814bc073822bbf7cfd270c4b0f71b112a04ebbe5f",
"feed_timezone": "2cc659e76a7f6f83f57ff4b0d171edfbbdbc62a1a0d798185525836b42f6ecb1",
"get_db": "c77eb1c621611e31ab28319b17065da64d30533baa04c2a5aeba743a293bbb0c",
"has_tables": "9f5ad997bff49fb9276e0cf612de0e9a88eca2fccdac8692b088f71ad2b30129",
"hash_password": "40ddad2e156cf6dfc9fb4ee36f35e0d08ad8d541989b209fcab568d4204cc0d9",
"head_revision": "7d5edc10e7ee8f86a7dd77c28803c61b1adf88a649d74987f924bef8b1e0e073",
"health": "53803ecc82d556044c97eaf1c54171a0f2cccb6bb9b4d0ec0dea11d30e2f8623",
"init_db": "4d6e2c0fc4f010daa7cd8df474365f003b816d52345294d42e09fab0314f79c2",
"iter_month_dates": "5bdf4897859c15f5a3c3a66c80316d261831dbb4ae2faa36872202feec22d383",
"lifespan": "81b73c45932fcba7199a0d47ff4536f9961c293a7396f2cf82a911b48ac93b1e",
"login": "f0fa25a608d15dfaeec681697ddc9f5d49bfe302f7a5a017c4cee68d99c90db3",
"login_page": "465ec7327c51717a94fa4020bc3e4ca5d8af5d2f30352e87434f212ae999f054",
"logout": "60430dd402ba181a2aa2da2f8716ea3f4581a7d68ffe9156724be0446b213ec5",
"main": "f678f9d42b7760d3adb188e28bd32e1f437dcf373edd8433ba585c602deba288",
"migrate": "0f75de0991513bd44872cbd512b4fb34e266aa5894833f6efde1ae3a37acc516",
"moment": "236fb581bc10ba7d82702f3205fdd76475b7a0d2355229017dd2216bacb8a525",
"month_bounds": "0b1a85e2ad4419670077ed19ef9305e2b2ba61f43c86db27b931d1c7c21f06fe",
"month_name": "25928f05d2b10db526e15d96c442707d3d370e470cdc40a7a76f22905594d06b",
"new_csrf_token": "59bd529fd8605261ff2dac1348bd808cd1bfab8a062a68a0218e1fc8b65abd64",
"new_swap_page": "62fb0393a48703ad89379e6c1e12fe30797a3970366f97cb209ccaadd741b45d",
"notification_status_label": "1b63a299a84917b66e61ea76d42bc806e13fc147df5c25466f09efafaa4aeabe",
"pattern_team_for_date": "febe4323146b290652d2995078eda9f8b5761ea99fc267bd4ca9ffab9a78aecf",
"pattern_teams": "a1bbd5380c67eb9e142ec4a105fd3bdf0243e8d7642fcc24d0eb7b024ee8347c",
"reject_swap": "fc96bdd9d2ac28f6c46fd9a314c918f17c44d963beda0dd15a8511490091aa83",
"revert_swap": "c033fc0d5368da573ca33e41df6463c029fd96d61e2e36775149a98e221b79bf",
"rotation_team_for_date": "e6015fbc5d2bc3e152a02e22b3ea8f2cbb9075c2817f5e4f711048d16b180eb4",
"run_migrations": "9db018c5ab941a1fd5fee9b233b92ad649e2416cbf8ae605c8fc4b4b35712c9e",
"seed_demo": "d6141cc2710340cfc9ca3282b2d4a8018461d7134f4ab351a9d9dd653aeb40df",
"send_email_notification": "438763f138dc3103beaf1c3eac3e8a5501b57c8b1f520888c8da27e24c087c0a",
"send_many": "43af810d2e2a4f066c1785f6d0ae6738bec3063d95146c95076b84c9f7d13a4f",
"send_reminders": "351f084c07dcb6dc4433b43eb4493b195bfaaab7d8627774bd3ca0776e6a4912",
"source_label": "2b6551908dc0c237830b10aa17edee9822150f6c67cac4e9d53f2fbf62a8fc35",
"stamp": "953ddcd55bb2f00e293f821a5583565ef793704dffcfc85e0b7a5a95241acae1",
"swap_status_label": "bf46d5b8e6e685882063adbd33b363f39479e5f7e24ce2aeb970924d4fb5216b",
"swaps_page": "bb2bc978469a6e93f909d6a3c20af4aeaf5397c55013bc7edd85e7714ec9c020",
"verify_password": "bcc868c5552a7003a2fa8798c81403aa21d7fe5c35c7ecc81d9c29effaf8f974",
"weekday_name": "bad38b61f1dd398814f0033823c70ea9b0d57a638a0bf78fb9ff806c05da9e92",
"weekday_names": "a9d41b91b2d5b7b057f9b3325cf970df90a992348af215425119dfd7854096ac",
"window_label": "52b13470c59d8246d8ef3d28ffa0adae728eceef25e77320c388a26a28ef7fa0"
},
"migrations": {
"README": "31595cf53626af9ed16e15c44fa43183209cc163fbc3ebcb904b22ac436a8884",
"env.py": "2094c197e04306806e783814065c4a47f98dd7764315ffac6e676130c0ee202e",
"script.py.mako": "d38920781b4d31ae1c0a71bc09f41c2ade8feab9e5a087e3f30cb4bbf0c1c9d3",
"versions/0001_initial.py": "6b5f1c37860c1cde017565a39464bf9b2635840c695f1f06a874845e3fa3ff6a"
},
"openapi": {
"components": {
"schemas": {
"Body_accept_swap_swaps__swap_id__accept_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
}
},
"required": [
"csrf_token"
],
"title": "Body_accept_swap_swaps__swap_id__accept_post",
"type": "object"
},
"Body_account_password_account_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
},
"new_password": {
"default": "",
"title": "New Password",
"type": "string"
}
},
"required": [
"csrf_token"
],
"title": "Body_account_password_account_post",
"type": "object"
},
"Body_admin_apply_pattern_admin_schedules__schedule_id__apply_pattern_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
},
"month": {
"title": "Month",
"type": "integer"
},
"year": {
"title": "Year",
"type": "integer"
}
},
"required": [
"year",
"month",
"csrf_token"
],
"title": "Body_admin_apply_pattern_admin_schedules__schedule_id__apply_pattern_post",
"type": "object"
},
"Body_admin_apply_rotation_admin_schedules__schedule_id__apply_rotation_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
},
"month": {
"title": "Month",
"type": "integer"
},
"year": {
"title": "Year",
"type": "integer"
}
},
"required": [
"year",
"month",
"csrf_token"
],
"title": "Body_admin_apply_rotation_admin_schedules__schedule_id__apply_rotation_post",
"type": "object"
},
"Body_admin_assign_team_admin_assign_post": {
"properties": {
"assignment_id": {
"title": "Assignment Id",
"type": "integer"
},
"csrf_token": {
"title": "Csrf Token",
"type": "string"
},
"team_id": {
"default": "",
"title": "Team Id",
"type": "string"
}
},
"required": [
"assignment_id",
"csrf_token"
],
"title": "Body_admin_assign_team_admin_assign_post",
"type": "object"
},
"Body_admin_assignment_regenerate_admin_schedules__schedule_id__assignments__assignment_id__regenerate_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
}
},
"required": [
"csrf_token"
],
"title": "Body_admin_assignment_regenerate_admin_schedules__schedule_id__assignments__assignment_id__regenerate_post",
"type": "object"
},
"Body_admin_assignment_regenerate_admin_schedules__schedule_id__assignments__assignment_id__use_rotation_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
}
},
"required": [
"csrf_token"
],
"title": "Body_admin_assignment_regenerate_admin_schedules__schedule_id__assignments__assignment_id__use_rotation_post",
"type": "object"
},
"Body_admin_clear_from_admin_schedules__schedule_id__clear_from_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
},
"month": {
"title": "Month",
"type": "integer"
},
"year": {
"title": "Year",
"type": "integer"
}
},
"required": [
"year",
"month",
"csrf_token"
],
"title": "Body_admin_clear_from_admin_schedules__schedule_id__clear_from_post",
"type": "object"
},
"Body_admin_create_team_admin_teams_post": {
"properties": {
"assigned_user_id": {
"default": "",
"title": "Assigned User Id",
"type": "string"
},
"csrf_token": {
"title": "Csrf Token",
"type": "string"
},
"email": {
"default": "",
"title": "Email",
"type": "string"
},
"name": {
"title": "Name",
"type": "string"
},
"notify_email": {
"default": "",
"title": "Notify Email",
"type": "string"
},
"phone": {
"default": "",
"title": "Phone",
"type": "string"
}
},
"required": [
"name",
"csrf_token"
],
"title": "Body_admin_create_team_admin_teams_post",
"type": "object"
},
"Body_admin_create_user_admin_users_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
},
"is_admin": {
"default": "",
"title": "Is Admin",
"type": "string"
},
"name": {
"title": "Name",
"type": "string"
},
"password": {
"title": "Password",
"type": "string"
},
"team_ids": {
"default": [],
"items": {
"type": "string"
},
"title": "Team Ids",
"type": "array"
},
"username": {
"title": "Username",
"type": "string"
}
},
"required": [
"name",
"username",
"password",
"csrf_token"
],
"title": "Body_admin_create_user_admin_users_post",
"type": "object"
},
"Body_admin_reject_swap_swaps__swap_id__admin_reject_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
}
},
"required": [
"csrf_token"
],
"title": "Body_admin_reject_swap_swaps__swap_id__admin_reject_post",
"type": "object"
},
"Body_admin_rotation_add_admin_schedules__schedule_id__rotation_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
},
"team_id": {
"title": "Team Id",
"type": "integer"
}
},
"required": [
"team_id",
"csrf_token"
],
"title": "Body_admin_rotation_add_admin_schedules__schedule_id__rotation_post",
"type": "object"
},
"Body_admin_rotation_move_admin_rotation__member_id__move_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
},
"direction": {
"title": "Direction",
"type": "string"
}
},
"required": [
"direction",
"csrf_token"
],
"title": "Body_admin_rotation_move_admin_rotation__member_id__move_post",
"type": "object"
},
"Body_admin_rotation_remove_admin_rotation__member_id__remove_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
}
},
"required": [
"csrf_token"
],
"title": "Body_admin_rotation_remove_admin_rotation__member_id__remove_post",
"type": "object"
},
"Body_admin_schedule_assign_swap_admin_schedules__schedule_id__swaps__swap_id__assign_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
},
"month": {
"default": "",
"title": "Month",
"type": "string"
},
"team_id": {
"title": "Team Id",
"type": "integer"
},
"year": {
"default": "",
"title": "Year",
"type": "string"
}
},
"required": [
"team_id",
"csrf_token"
],
"title": "Body_admin_schedule_assign_swap_admin_schedules__schedule_id__swaps__swap_id__assign_post",
"type": "object"
},
"Body_admin_schedule_settings_admin_schedules__schedule_id__settings_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
},
"end_time": {
"default": "",
"title": "End Time",
"type": "string"
},
"is_active": {
"default": "",
"title": "Is Active",
"type": "string"
},
"requires_manager_approval": {
"default": "",
"title": "Requires Manager Approval",
"type": "string"
},
"start_time": {
"default": "",
"title": "Start Time",
"type": "string"
}
},
"required": [
"csrf_token"
],
"title": "Body_admin_schedule_settings_admin_schedules__schedule_id__settings_post",
"type": "object"
},
"Body_admin_team_delete_admin_teams__team_id__delete_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
}
},
"required": [
"csrf_token"
],
"title": "Body_admin_team_delete_admin_teams__team_id__delete_post",
"type": "object"
},
"Body_admin_update_team_admin_teams__team_id__update_post": {
"properties": {
"assigned_user_id": {
"default": "",
"title": "Assigned User Id",
"type": "string"
},
"csrf_token": {
"title": "Csrf Token",
"type": "string"
},
"email": {
"default": "",
"title": "Email",
"type": "string"
},
"is_active": {
"default": "",
"title": "Is Active",
"type": "string"
},
"name": {
"title": "Name",
"type": "string"
},
"notify_email": {
"default": "",
"title": "Notify Email",
"type": "string"
},
"phone": {
"default": "",
"title": "Phone",
"type": "string"
}
},
"required": [
"name",
"csrf_token"
],
"title": "Body_admin_update_team_admin_teams__team_id__update_post",
"type": "object"
},
"Body_admin_update_user_admin_users__user_id__update_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
},
"is_active": {
"default": "",
"title": "Is Active",
"type": "string"
},
"is_admin": {
"default": "",
"title": "Is Admin",
"type": "string"
},
"name": {
"title": "Name",
"type": "string"
},
"password": {
"default": "",
"title": "Password",
"type": "string"
},
"team_ids": {
"default": [],
"items": {
"type": "string"
},
"title": "Team Ids",
"type": "array"
},
"username": {
"title": "Username",
"type": "string"
}
},
"required": [
"name",
"username",
"csrf_token"
],
"title": "Body_admin_update_user_admin_users__user_id__update_post",
"type": "object"
},
"Body_admin_user_delete_admin_users__user_id__delete_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
}
},
"required": [
"csrf_token"
],
"title": "Body_admin_user_delete_admin_users__user_id__delete_post",
"type": "object"
},
"Body_approve_swap_swaps__swap_id__approve_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
},
"team_id": {
"default": "",
"title": "Team Id",
"type": "string"
}
},
"required": [
"csrf_token"
],
"title": "Body_approve_swap_swaps__swap_id__approve_post",
"type": "object"
},
"Body_cancel_swap_swaps__swap_id__cancel_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
}
},
"required": [
"csrf_token"
],
"title": "Body_cancel_swap_swaps__swap_id__cancel_post",
"type": "object"
},
"Body_create_swap_assignments__assignment_id__swap_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
},
"message": {
"default": "",
"title": "Message",
"type": "string"
},
"target_assignment_id": {
"title": "Target Assignment Id",
"type": "integer"
}
},
"required": [
"target_assignment_id",
"csrf_token"
],
"title": "Body_create_swap_assignments__assignment_id__swap_post",
"type": "object"
},
"Body_create_swap_for_chosen_shift_swaps_new_post": {
"properties": {
"assignment_id": {
"title": "Assignment Id",
"type": "integer"
},
"csrf_token": {
"title": "Csrf Token",
"type": "string"
},
"message": {
"default": "",
"title": "Message",
"type": "string"
},
"target_assignment_id": {
"title": "Target Assignment Id",
"type": "integer"
}
},
"required": [
"assignment_id",
"target_assignment_id",
"csrf_token"
],
"title": "Body_create_swap_for_chosen_shift_swaps_new_post",
"type": "object"
},
"Body_login_login_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
},
"password": {
"title": "Password",
"type": "string"
},
"username": {
"title": "Username",
"type": "string"
}
},
"required": [
"username",
"password",
"csrf_token"
],
"title": "Body_login_login_post",
"type": "object"
},
"Body_logout_logout_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
}
},
"required": [
"csrf_token"
],
"title": "Body_logout_logout_post",
"type": "object"
},
"Body_reject_swap_swaps__swap_id__reject_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
}
},
"required": [
"csrf_token"
],
"title": "Body_reject_swap_swaps__swap_id__reject_post",
"type": "object"
},
"Body_revert_swap_swaps__swap_id__revert_post": {
"properties": {
"csrf_token": {
"title": "Csrf Token",
"type": "string"
}
},
"required": [
"csrf_token"
],
"title": "Body_revert_swap_swaps__swap_id__revert_post",
"type": "object"
},
"HTTPValidationError": {
"properties": {
"detail": {
"items": {
"$ref": "#/components/schemas/ValidationError"
},
"title": "Detail",
"type": "array"
}
},
"title": "HTTPValidationError",
"type": "object"
},
"ValidationError": {
"properties": {
"ctx": {
"title": "Context",
"type": "object"
},
"input": {
"title": "Input"
},
"loc": {
"items": {
"anyOf": [
{
"type": "string"
},
{
"type": "integer"
}
]
},
"title": "Location",
"type": "array"
},
"msg": {
"title": "Message",
"type": "string"
},
"type": {
"title": "Error Type",
"type": "string"
}
},
"required": [
"loc",
"msg",
"type"
],
"title": "ValidationError",
"type": "object"
}
}
},
"info": {
"title": "Contínuos CRR",
"version": "0.1.0"
},
"openapi": "3.1.0",
"paths": {
"/": {
"get": {
"operationId": "dashboard__get",
"parameters": [
{
"in": "query",
"name": "year",
"required": false,
"schema": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Year"
}
},
{
"in": "query",
"name": "month",
"required": false,
"schema": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Month"
}
}
],
"responses": {
"200": {
"content": {
"text/html": {
"schema": {
"type": "string"
}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Dashboard"
}
},
"/account": {
"get": {
"description": "The password editor lives on its own page, reached by clicking the user name.",
"operationId": "account_page_account_get",
"responses": {
"200": {
"content": {
"text/html": {
"schema": {
"type": "string"
}
}
},
"description": "Successful Response"
}
},
"summary": "Account Page"
},
"post": {
"description": "A user changes its own password from `/account`: one field, no ceremony.",
"operationId": "account_password_account_post",
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_account_password_account_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Account Password"
}
},
"/admin/assign": {
"get": {
"description": "Pick a day and put someone on one of its shifts. No change request involved.\n\n`on` is a whole ISO date (`YYYY-MM-DD`), which is what the date input sends.",
"operationId": "admin_assign_day_admin_assign_get",
"parameters": [
{
"in": "query",
"name": "on",
"required": false,
"schema": {
"anyOf": [
{
"type": "string"
},
{
"type": "null"
}
],
"title": "On"
}
}
],
"responses": {
"200": {
"content": {
"text/html": {
"schema": {
"type": "string"
}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Assign Day"
},
"post": {
"operationId": "admin_assign_team_admin_assign_post",
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_admin_assign_team_admin_assign_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Assign Team"
}
},
"/admin/month": {
"get": {
"operationId": "admin_month_admin_month_get",
"responses": {
"200": {
"content": {
"text/html": {
"schema": {
"type": "string"
}
}
},
"description": "Successful Response"
}
},
"summary": "Admin Month"
}
},
"/admin/notifications": {
"get": {
"operationId": "admin_notifications_admin_notifications_get",
"responses": {
"200": {
"content": {
"text/html": {
"schema": {
"type": "string"
}
}
},
"description": "Successful Response"
}
},
"summary": "Admin Notifications"
}
},
"/admin/rotation/{member_id}/move": {
"post": {
"operationId": "admin_rotation_move_admin_rotation__member_id__move_post",
"parameters": [
{
"in": "path",
"name": "member_id",
"required": true,
"schema": {
"title": "Member Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_admin_rotation_move_admin_rotation__member_id__move_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Rotation Move"
}
},
"/admin/rotation/{member_id}/remove": {
"post": {
"operationId": "admin_rotation_remove_admin_rotation__member_id__remove_post",
"parameters": [
{
"in": "path",
"name": "member_id",
"required": true,
"schema": {
"title": "Member Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_admin_rotation_remove_admin_rotation__member_id__remove_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Rotation Remove"
}
},
"/admin/schedules": {
"get": {
"operationId": "admin_schedules_admin_schedules_get",
"parameters": [
{
"in": "query",
"name": "schedule_id",
"required": false,
"schema": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Schedule Id"
}
}
],
"responses": {
"200": {
"content": {
"text/html": {
"schema": {
"type": "string"
}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Schedules"
}
},
"/admin/schedules/{schedule_id}": {
"get": {
"operationId": "admin_schedule_admin_schedules__schedule_id__get",
"parameters": [
{
"in": "path",
"name": "schedule_id",
"required": true,
"schema": {
"title": "Schedule Id",
"type": "integer"
}
},
{
"in": "query",
"name": "year",
"required": false,
"schema": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Year"
}
},
{
"in": "query",
"name": "month",
"required": false,
"schema": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Month"
}
}
],
"responses": {
"200": {
"content": {
"text/html": {
"schema": {
"type": "string"
}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Schedule"
}
},
"/admin/schedules/{schedule_id}/apply-pattern": {
"post": {
"operationId": "admin_apply_pattern_admin_schedules__schedule_id__apply_pattern_post",
"parameters": [
{
"in": "path",
"name": "schedule_id",
"required": true,
"schema": {
"title": "Schedule Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_admin_apply_pattern_admin_schedules__schedule_id__apply_pattern_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Apply Pattern"
}
},
"/admin/schedules/{schedule_id}/apply-rotation": {
"post": {
"operationId": "admin_apply_rotation_admin_schedules__schedule_id__apply_rotation_post",
"parameters": [
{
"in": "path",
"name": "schedule_id",
"required": true,
"schema": {
"title": "Schedule Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_admin_apply_rotation_admin_schedules__schedule_id__apply_rotation_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Apply Rotation"
}
},
"/admin/schedules/{schedule_id}/assignments": {
"post": {
"operationId": "admin_schedule_assignments_admin_schedules__schedule_id__assignments_post",
"parameters": [
{
"in": "path",
"name": "schedule_id",
"required": true,
"schema": {
"title": "Schedule Id",
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Schedule Assignments"
}
},
"/admin/schedules/{schedule_id}/assignments/{assignment_id}/regenerate": {
"post": {
"operationId": "admin_assignment_regenerate_admin_schedules__schedule_id__assignments__assignment_id__regenerate_post",
"parameters": [
{
"in": "path",
"name": "schedule_id",
"required": true,
"schema": {
"title": "Schedule Id",
"type": "integer"
}
},
{
"in": "path",
"name": "assignment_id",
"required": true,
"schema": {
"title": "Assignment Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_admin_assignment_regenerate_admin_schedules__schedule_id__assignments__assignment_id__regenerate_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Assignment Regenerate"
}
},
"/admin/schedules/{schedule_id}/assignments/{assignment_id}/use-rotation": {
"post": {
"operationId": "admin_assignment_regenerate_admin_schedules__schedule_id__assignments__assignment_id__use_rotation_post",
"parameters": [
{
"in": "path",
"name": "schedule_id",
"required": true,
"schema": {
"title": "Schedule Id",
"type": "integer"
}
},
{
"in": "path",
"name": "assignment_id",
"required": true,
"schema": {
"title": "Assignment Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_admin_assignment_regenerate_admin_schedules__schedule_id__assignments__assignment_id__use_rotation_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Assignment Regenerate"
}
},
"/admin/schedules/{schedule_id}/clear-from": {
"post": {
"operationId": "admin_clear_from_admin_schedules__schedule_id__clear_from_post",
"parameters": [
{
"in": "path",
"name": "schedule_id",
"required": true,
"schema": {
"title": "Schedule Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_admin_clear_from_admin_schedules__schedule_id__clear_from_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Clear From"
}
},
"/admin/schedules/{schedule_id}/pattern": {
"post": {
"operationId": "admin_schedule_pattern_admin_schedules__schedule_id__pattern_post",
"parameters": [
{
"in": "path",
"name": "schedule_id",
"required": true,
"schema": {
"title": "Schedule Id",
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Schedule Pattern"
}
},
"/admin/schedules/{schedule_id}/rotation": {
"post": {
"operationId": "admin_rotation_add_admin_schedules__schedule_id__rotation_post",
"parameters": [
{
"in": "path",
"name": "schedule_id",
"required": true,
"schema": {
"title": "Schedule Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_admin_rotation_add_admin_schedules__schedule_id__rotation_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Rotation Add"
}
},
"/admin/schedules/{schedule_id}/settings": {
"post": {
"operationId": "admin_schedule_settings_admin_schedules__schedule_id__settings_post",
"parameters": [
{
"in": "path",
"name": "schedule_id",
"required": true,
"schema": {
"title": "Schedule Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_admin_schedule_settings_admin_schedules__schedule_id__settings_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Schedule Settings"
}
},
"/admin/schedules/{schedule_id}/swaps/{swap_id}/assign": {
"post": {
"description": "Complete a change request on behalf of the rota, including for rota-only teams.",
"operationId": "admin_schedule_assign_swap_admin_schedules__schedule_id__swaps__swap_id__assign_post",
"parameters": [
{
"in": "path",
"name": "schedule_id",
"required": true,
"schema": {
"title": "Schedule Id",
"type": "integer"
}
},
{
"in": "path",
"name": "swap_id",
"required": true,
"schema": {
"title": "Swap Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_admin_schedule_assign_swap_admin_schedules__schedule_id__swaps__swap_id__assign_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Schedule Assign Swap"
}
},
"/admin/teams": {
"get": {
"description": "Who works the rota. Users are not created here: a team only picks who signs in for it.",
"operationId": "admin_teams_admin_teams_get",
"responses": {
"200": {
"content": {
"text/html": {
"schema": {
"type": "string"
}
}
},
"description": "Successful Response"
}
},
"summary": "Admin Teams"
},
"post": {
"description": "Add a team to the rota. The user who signs in for it is chosen here or later.",
"operationId": "admin_create_team_admin_teams_post",
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_admin_create_team_admin_teams_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Create Team"
}
},
"/admin/teams/{team_id}/delete": {
"get": {
"operationId": "admin_team_delete_page_admin_teams__team_id__delete_get",
"parameters": [
{
"in": "path",
"name": "team_id",
"required": true,
"schema": {
"title": "Team Id",
"type": "integer"
}
}
],
"responses": {
"200": {
"content": {
"text/html": {
"schema": {
"type": "string"
}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Team Delete Page"
},
"post": {
"operationId": "admin_team_delete_admin_teams__team_id__delete_post",
"parameters": [
{
"in": "path",
"name": "team_id",
"required": true,
"schema": {
"title": "Team Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_admin_team_delete_admin_teams__team_id__delete_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Team Delete"
}
},
"/admin/teams/{team_id}/update": {
"post": {
"operationId": "admin_update_team_admin_teams__team_id__update_post",
"parameters": [
{
"in": "path",
"name": "team_id",
"required": true,
"schema": {
"title": "Team Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_admin_update_team_admin_teams__team_id__update_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Update Team"
}
},
"/admin/users": {
"get": {
"description": "The users: created here, then assigned to the teams they sign in for.",
"operationId": "admin_users_admin_users_get",
"responses": {
"200": {
"content": {
"text/html": {
"schema": {
"type": "string"
}
}
},
"description": "Successful Response"
}
},
"summary": "Admin Users"
},
"post": {
"description": "A new user, assigned to the teams it will sign in for.",
"operationId": "admin_create_user_admin_users_post",
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_admin_create_user_admin_users_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Create User"
}
},
"/admin/users/{user_id}/delete": {
"post": {
"operationId": "admin_user_delete_admin_users__user_id__delete_post",
"parameters": [
{
"in": "path",
"name": "user_id",
"required": true,
"schema": {
"title": "User Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_admin_user_delete_admin_users__user_id__delete_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin User Delete"
}
},
"/admin/users/{user_id}/update": {
"post": {
"operationId": "admin_update_user_admin_users__user_id__update_post",
"parameters": [
{
"in": "path",
"name": "user_id",
"required": true,
"schema": {
"title": "User Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_admin_update_user_admin_users__user_id__update_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Update User"
}
},
"/assignments/{assignment_id}/swap": {
"post": {
"operationId": "create_swap_assignments__assignment_id__swap_post",
"parameters": [
{
"in": "path",
"name": "assignment_id",
"required": true,
"schema": {
"title": "Assignment Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_create_swap_assignments__assignment_id__swap_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Create Swap"
}
},
"/calendar": {
"get": {
"description": "The links to subscribe, kept off the rota so that page stays about the shifts.",
"operationId": "calendar_page_calendar_get",
"responses": {
"200": {
"content": {
"text/html": {
"schema": {
"type": "string"
}
}
},
"description": "Successful Response"
}
},
"summary": "Calendar Page"
}
},
"/calendar/{token}.ics": {
"get": {
"description": "Subscribe from any calendar app: one feed per team, plus a shared whole-rota feed.",
"operationId": "calendar_feed_calendar__token__ics_get",
"parameters": [
{
"in": "path",
"name": "token",
"required": true,
"schema": {
"title": "Token",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Calendar Feed"
}
},
"/export/schedule": {
"get": {
"description": "Choose a schedule and exact dates before downloading its PDF.",
"operationId": "export_schedule_export_schedule_get",
"parameters": [
{
"in": "query",
"name": "schedule_id",
"required": false,
"schema": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Schedule Id"
}
}
],
"responses": {
"200": {
"content": {
"text/html": {
"schema": {
"type": "string"
}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Export Schedule"
}
},
"/export/schedule.pdf": {
"get": {
"operationId": "download_schedule_pdf_export_schedule_pdf_get",
"parameters": [
{
"in": "query",
"name": "schedule_id",
"required": true,
"schema": {
"title": "Schedule Id",
"type": "integer"
}
},
{
"in": "query",
"name": "start_date",
"required": true,
"schema": {
"format": "date",
"title": "Start Date",
"type": "string"
}
},
{
"in": "query",
"name": "end_date",
"required": true,
"schema": {
"format": "date",
"title": "End Date",
"type": "string"
}
}
],
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Download Schedule Pdf"
}
},
"/health": {
"get": {
"operationId": "health_health_get",
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
}
},
"summary": "Health"
}
},
"/login": {
"get": {
"operationId": "login_page_login_get",
"responses": {
"200": {
"content": {
"text/html": {
"schema": {
"type": "string"
}
}
},
"description": "Successful Response"
}
},
"summary": "Login Page"
},
"post": {
"operationId": "login_login_post",
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_login_login_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Login"
}
},
"/logout": {
"post": {
"operationId": "logout_logout_post",
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_logout_logout_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Logout"
}
},
"/swaps": {
"get": {
"operationId": "swaps_page_swaps_get",
"responses": {
"200": {
"content": {
"text/html": {
"schema": {
"type": "string"
}
}
},
"description": "Successful Response"
}
},
"summary": "Swaps Page"
}
},
"/swaps/new": {
"get": {
"description": "Its own page, so the rota keeps showing only who works each day.",
"operationId": "new_swap_page_swaps_new_get",
"parameters": [
{
"in": "query",
"name": "year",
"required": false,
"schema": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Year"
}
},
{
"in": "query",
"name": "month",
"required": false,
"schema": {
"anyOf": [
{
"type": "integer"
},
{
"type": "null"
}
],
"title": "Month"
}
}
],
"responses": {
"200": {
"content": {
"text/html": {
"schema": {
"type": "string"
}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "New Swap Page"
},
"post": {
"operationId": "create_swap_for_chosen_shift_swaps_new_post",
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_create_swap_for_chosen_shift_swaps_new_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Create Swap For Chosen Shift"
}
},
"/swaps/{swap_id}/accept": {
"post": {
"operationId": "accept_swap_swaps__swap_id__accept_post",
"parameters": [
{
"in": "path",
"name": "swap_id",
"required": true,
"schema": {
"title": "Swap Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_accept_swap_swaps__swap_id__accept_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Accept Swap"
}
},
"/swaps/{swap_id}/admin-reject": {
"post": {
"operationId": "admin_reject_swap_swaps__swap_id__admin_reject_post",
"parameters": [
{
"in": "path",
"name": "swap_id",
"required": true,
"schema": {
"title": "Swap Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_admin_reject_swap_swaps__swap_id__admin_reject_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Admin Reject Swap"
}
},
"/swaps/{swap_id}/approve": {
"post": {
"operationId": "approve_swap_swaps__swap_id__approve_post",
"parameters": [
{
"in": "path",
"name": "swap_id",
"required": true,
"schema": {
"title": "Swap Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_approve_swap_swaps__swap_id__approve_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Approve Swap"
}
},
"/swaps/{swap_id}/cancel": {
"post": {
"operationId": "cancel_swap_swaps__swap_id__cancel_post",
"parameters": [
{
"in": "path",
"name": "swap_id",
"required": true,
"schema": {
"title": "Swap Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_cancel_swap_swaps__swap_id__cancel_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Cancel Swap"
}
},
"/swaps/{swap_id}/reject": {
"post": {
"operationId": "reject_swap_swaps__swap_id__reject_post",
"parameters": [
{
"in": "path",
"name": "swap_id",
"required": true,
"schema": {
"title": "Swap Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_reject_swap_swaps__swap_id__reject_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Reject Swap"
}
},
"/swaps/{swap_id}/revert": {
"post": {
"description": "Administrator only: undo an approved swap and give both shifts back.",
"operationId": "revert_swap_swaps__swap_id__revert_post",
"parameters": [
{
"in": "path",
"name": "swap_id",
"required": true,
"schema": {
"title": "Swap Id",
"type": "integer"
}
}
],
"requestBody": {
"content": {
"application/x-www-form-urlencoded": {
"schema": {
"$ref": "#/components/schemas/Body_revert_swap_swaps__swap_id__revert_post"
}
}
},
"required": true
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {}
}
},
"description": "Successful Response"
},
"422": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/HTTPValidationError"
}
}
},
"description": "Validation Error"
}
},
"summary": "Revert Swap"
}
}
}
},
"orm": {
"relationships": {
"Assignment": {
"schedule": {
"back_populates": null,
"cascade": [
"merge",
"save-update"
],
"order_by": [],
"pairs": [
[
"assignments.schedule_id",
"schedules.id"
]
],
"target": "Schedule",
"uselist": false
},
"team": {
"back_populates": null,
"cascade": [
"merge",
"save-update"
],
"order_by": [],
"pairs": [
[
"assignments.team_id",
"teams.id"
]
],
"target": "Team",
"uselist": false
}
},
"MonthlyPattern": {
"schedule": {
"back_populates": "pattern_days",
"cascade": [
"merge",
"save-update"
],
"order_by": [],
"pairs": [
[
"monthly_patterns.schedule_id",
"schedules.id"
]
],
"target": "Schedule",
"uselist": false
},
"team": {
"back_populates": null,
"cascade": [
"merge",
"save-update"
],
"order_by": [],
"pairs": [
[
"monthly_patterns.team_id",
"teams.id"
]
],
"target": "Team",
"uselist": false
}
},
"NotificationLog": {
"team": {
"back_populates": null,
"cascade": [
"merge",
"save-update"
],
"order_by": [],
"pairs": [
[
"notification_logs.team_id",
"teams.id"
]
],
"target": "Team",
"uselist": false
}
},
"RotationMember": {
"schedule": {
"back_populates": "rotation_members",
"cascade": [
"merge",
"save-update"
],
"order_by": [],
"pairs": [
[
"rotation_members.schedule_id",
"schedules.id"
]
],
"target": "Schedule",
"uselist": false
},
"team": {
"back_populates": null,
"cascade": [
"merge",
"save-update"
],
"order_by": [],
"pairs": [
[
"rotation_members.team_id",
"teams.id"
]
],
"target": "Team",
"uselist": false
}
},
"Schedule": {
"pattern_days": {
"back_populates": "schedule",
"cascade": [
"delete",
"delete-orphan",
"expunge",
"merge",
"refresh-expire",
"save-update"
],
"order_by": [
"monthly_patterns.day_of_month"
],
"pairs": [
[
"schedules.id",
"monthly_patterns.schedule_id"
]
],
"target": "MonthlyPattern",
"uselist": true
},
"rotation_members": {
"back_populates": "schedule",
"cascade": [
"delete",
"delete-orphan",
"expunge",
"merge",
"refresh-expire",
"save-update"
],
"order_by": [
"rotation_members.position"
],
"pairs": [
[
"schedules.id",
"rotation_members.schedule_id"
]
],
"target": "RotationMember",
"uselist": true
}
},
"SwapRequest": {
"accepted_by": {
"back_populates": null,
"cascade": [
"merge",
"save-update"
],
"order_by": [],
"pairs": [
[
"swap_requests.accepted_team_id",
"teams.id"
]
],
"target": "Team",
"uselist": false
},
"assignment": {
"back_populates": null,
"cascade": [
"merge",
"save-update"
],
"order_by": [],
"pairs": [
[
"swap_requests.assignment_id",
"assignments.id"
]
],
"target": "Assignment",
"uselist": false
},
"requester": {
"back_populates": null,
"cascade": [
"merge",
"save-update"
],
"order_by": [],
"pairs": [
[
"swap_requests.requester_id",
"teams.id"
]
],
"target": "Team",
"uselist": false
},
"target_assignment": {
"back_populates": null,
"cascade": [
"merge",
"save-update"
],
"order_by": [],
"pairs": [
[
"swap_requests.target_assignment_id",
"assignments.id"
]
],
"target": "Assignment",
"uselist": false
},
"target_team": {
"back_populates": null,
"cascade": [
"merge",
"save-update"
],
"order_by": [],
"pairs": [
[
"swap_requests.target_team_id",
"teams.id"
]
],
"target": "Team",
"uselist": false
}
},
"Team": {
"user": {
"back_populates": "teams",
"cascade": [
"merge",
"save-update"
],
"order_by": [],
"pairs": [
[
"teams.user_id",
"users.id"
]
],
"target": "User",
"uselist": false
}
},
"User": {
"teams": {
"back_populates": "user",
"cascade": [
"merge",
"save-update"
],
"order_by": [],
"pairs": [
[
"users.id",
"teams.user_id"
]
],
"target": "Team",
"uselist": true
}
}
},
"tables": {
"assignments": {
"columns": [
{
"default": null,
"name": "id",
"nullable": false,
"onupdate": null,
"primary_key": true,
"server_default": null,
"type": "INTEGER"
},
{
"default": null,
"name": "schedule_id",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "INTEGER"
},
{
"default": null,
"name": "date",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "DATE"
},
{
"default": null,
"name": "team_id",
"nullable": true,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "INTEGER"
},
{
"default": "generated",
"name": "source",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "VARCHAR(20)"
},
{
"default": null,
"name": "note",
"nullable": true,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "VARCHAR(500)"
},
{
"default": {
"callable": "datetime.utcnow"
},
"name": "updated_at",
"nullable": false,
"onupdate": {
"callable": "datetime.utcnow"
},
"primary_key": false,
"server_default": null,
"type": "DATETIME"
}
],
"constraints": [
{
"columns": [
"id"
],
"name": null,
"type": "PrimaryKeyConstraint"
},
{
"columns": [
"schedule_id",
"date"
],
"name": "uq_assignment_schedule_date",
"type": "UniqueConstraint"
},
{
"columns": [
"schedule_id"
],
"name": null,
"ondelete": "CASCADE",
"onupdate": null,
"targets": [
"schedules.id"
],
"type": "ForeignKeyConstraint"
},
{
"columns": [
"team_id"
],
"name": null,
"ondelete": "SET NULL",
"onupdate": null,
"targets": [
"teams.id"
],
"type": "ForeignKeyConstraint"
}
],
"indexes": [
{
"columns": [
"date"
],
"name": "ix_assignments_date",
"unique": false
}
]
},
"monthly_patterns": {
"columns": [
{
"default": null,
"name": "id",
"nullable": false,
"onupdate": null,
"primary_key": true,
"server_default": null,
"type": "INTEGER"
},
{
"default": null,
"name": "schedule_id",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "INTEGER"
},
{
"default": null,
"name": "day_of_month",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "INTEGER"
},
{
"default": null,
"name": "team_id",
"nullable": true,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "INTEGER"
}
],
"constraints": [
{
"columns": [
"id"
],
"name": null,
"type": "PrimaryKeyConstraint"
},
{
"columns": [
"schedule_id",
"day_of_month"
],
"name": "uq_pattern_schedule_day",
"type": "UniqueConstraint"
},
{
"columns": [
"schedule_id"
],
"name": null,
"ondelete": "CASCADE",
"onupdate": null,
"targets": [
"schedules.id"
],
"type": "ForeignKeyConstraint"
},
{
"columns": [
"team_id"
],
"name": null,
"ondelete": "SET NULL",
"onupdate": null,
"targets": [
"teams.id"
],
"type": "ForeignKeyConstraint"
},
{
"name": "ck_pattern_day_of_month",
"sql": "day_of_month BETWEEN 1 AND 31",
"type": "CheckConstraint"
}
],
"indexes": []
},
"notification_logs": {
"columns": [
{
"default": null,
"name": "id",
"nullable": false,
"onupdate": null,
"primary_key": true,
"server_default": null,
"type": "INTEGER"
},
{
"default": null,
"name": "team_id",
"nullable": true,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "INTEGER"
},
{
"default": null,
"name": "event_type",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "VARCHAR(60)"
},
{
"default": "email",
"name": "channel",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "VARCHAR(20)"
},
{
"default": null,
"name": "recipient",
"nullable": true,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "VARCHAR(254)"
},
{
"default": null,
"name": "subject",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "VARCHAR(250)"
},
{
"default": null,
"name": "body",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "TEXT"
},
{
"default": null,
"name": "status",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "VARCHAR(20)"
},
{
"default": null,
"name": "error",
"nullable": true,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "TEXT"
},
{
"default": {
"callable": "datetime.utcnow"
},
"name": "created_at",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "DATETIME"
},
{
"default": null,
"name": "sent_at",
"nullable": true,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "DATETIME"
}
],
"constraints": [
{
"columns": [
"id"
],
"name": null,
"type": "PrimaryKeyConstraint"
},
{
"columns": [
"team_id"
],
"name": null,
"ondelete": "SET NULL",
"onupdate": null,
"targets": [
"teams.id"
],
"type": "ForeignKeyConstraint"
}
],
"indexes": []
},
"rotation_members": {
"columns": [
{
"default": null,
"name": "id",
"nullable": false,
"onupdate": null,
"primary_key": true,
"server_default": null,
"type": "INTEGER"
},
{
"default": null,
"name": "schedule_id",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "INTEGER"
},
{
"default": null,
"name": "team_id",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "INTEGER"
},
{
"default": null,
"name": "position",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "INTEGER"
}
],
"constraints": [
{
"columns": [
"id"
],
"name": null,
"type": "PrimaryKeyConstraint"
},
{
"columns": [
"schedule_id",
"position"
],
"name": "uq_rotation_schedule_position",
"type": "UniqueConstraint"
},
{
"columns": [
"schedule_id",
"team_id"
],
"name": "uq_rotation_schedule_team",
"type": "UniqueConstraint"
},
{
"columns": [
"schedule_id"
],
"name": null,
"ondelete": "CASCADE",
"onupdate": null,
"targets": [
"schedules.id"
],
"type": "ForeignKeyConstraint"
},
{
"columns": [
"team_id"
],
"name": null,
"ondelete": "CASCADE",
"onupdate": null,
"targets": [
"teams.id"
],
"type": "ForeignKeyConstraint"
}
],
"indexes": []
},
"schedules": {
"columns": [
{
"default": null,
"name": "id",
"nullable": false,
"onupdate": null,
"primary_key": true,
"server_default": null,
"type": "INTEGER"
},
{
"default": null,
"name": "name",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "VARCHAR(120)"
},
{
"default": null,
"name": "slug",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "VARCHAR(80)"
},
{
"default": null,
"name": "schedule_type",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "VARCHAR(20)"
},
{
"default": null,
"name": "weekdays",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "VARCHAR(30)"
},
{
"default": "20:30:00",
"name": "start_time",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "TIME"
},
{
"default": "23:59:00",
"name": "end_time",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "TIME"
},
{
"default": "True",
"name": "requires_manager_approval",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "BOOLEAN"
},
{
"default": "True",
"name": "is_active",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "BOOLEAN"
},
{
"default": null,
"name": "rotation_anchor_date",
"nullable": true,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "DATE"
},
{
"default": {
"callable": "datetime.utcnow"
},
"name": "created_at",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "DATETIME"
}
],
"constraints": [
{
"columns": [
"id"
],
"name": null,
"type": "PrimaryKeyConstraint"
},
{
"columns": [
"name"
],
"name": null,
"type": "UniqueConstraint"
},
{
"columns": [
"slug"
],
"name": null,
"type": "UniqueConstraint"
}
],
"indexes": []
},
"swap_requests": {
"columns": [
{
"default": null,
"name": "id",
"nullable": false,
"onupdate": null,
"primary_key": true,
"server_default": null,
"type": "INTEGER"
},
{
"default": null,
"name": "assignment_id",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "INTEGER"
},
{
"default": null,
"name": "target_assignment_id",
"nullable": true,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "INTEGER"
},
{
"default": null,
"name": "requester_id",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "INTEGER"
},
{
"default": null,
"name": "target_team_id",
"nullable": true,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "INTEGER"
},
{
"default": null,
"name": "accepted_team_id",
"nullable": true,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "INTEGER"
},
{
"default": "open",
"name": "status",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "VARCHAR(30)"
},
{
"default": null,
"name": "message",
"nullable": true,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "VARCHAR(500)"
},
{
"default": {
"callable": "datetime.utcnow"
},
"name": "created_at",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "DATETIME"
},
{
"default": {
"callable": "datetime.utcnow"
},
"name": "updated_at",
"nullable": false,
"onupdate": {
"callable": "datetime.utcnow"
},
"primary_key": false,
"server_default": null,
"type": "DATETIME"
}
],
"constraints": [
{
"columns": [
"accepted_team_id"
],
"name": null,
"ondelete": "SET NULL",
"onupdate": null,
"targets": [
"teams.id"
],
"type": "ForeignKeyConstraint"
},
{
"columns": [
"assignment_id"
],
"name": null,
"ondelete": "CASCADE",
"onupdate": null,
"targets": [
"assignments.id"
],
"type": "ForeignKeyConstraint"
},
{
"columns": [
"id"
],
"name": null,
"type": "PrimaryKeyConstraint"
},
{
"columns": [
"requester_id"
],
"name": null,
"ondelete": "CASCADE",
"onupdate": null,
"targets": [
"teams.id"
],
"type": "ForeignKeyConstraint"
},
{
"columns": [
"target_assignment_id"
],
"name": null,
"ondelete": "SET NULL",
"onupdate": null,
"targets": [
"assignments.id"
],
"type": "ForeignKeyConstraint"
},
{
"columns": [
"target_team_id"
],
"name": null,
"ondelete": "SET NULL",
"onupdate": null,
"targets": [
"teams.id"
],
"type": "ForeignKeyConstraint"
}
],
"indexes": []
},
"teams": {
"columns": [
{
"default": null,
"name": "id",
"nullable": false,
"onupdate": null,
"primary_key": true,
"server_default": null,
"type": "INTEGER"
},
{
"default": null,
"name": "name",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "VARCHAR(120)"
},
{
"default": null,
"name": "user_id",
"nullable": true,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "INTEGER"
},
{
"default": null,
"name": "email",
"nullable": true,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "VARCHAR(254)"
},
{
"default": null,
"name": "phone",
"nullable": true,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "VARCHAR(40)"
},
{
"default": null,
"name": "calendar_token",
"nullable": true,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "VARCHAR(64)"
},
{
"default": "True",
"name": "is_active",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "BOOLEAN"
},
{
"default": "True",
"name": "notify_email",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "BOOLEAN"
},
{
"default": {
"callable": "datetime.utcnow"
},
"name": "created_at",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "DATETIME"
}
],
"constraints": [
{
"columns": [
"calendar_token"
],
"name": null,
"type": "UniqueConstraint"
},
{
"columns": [
"email"
],
"name": null,
"type": "UniqueConstraint"
},
{
"columns": [
"id"
],
"name": null,
"type": "PrimaryKeyConstraint"
},
{
"columns": [
"user_id"
],
"name": null,
"ondelete": "SET NULL",
"onupdate": null,
"targets": [
"users.id"
],
"type": "ForeignKeyConstraint"
}
],
"indexes": []
},
"users": {
"columns": [
{
"default": null,
"name": "id",
"nullable": false,
"onupdate": null,
"primary_key": true,
"server_default": null,
"type": "INTEGER"
},
{
"default": null,
"name": "name",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "VARCHAR(120)"
},
{
"default": null,
"name": "username",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "VARCHAR(80)"
},
{
"default": null,
"name": "password_hash",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "VARCHAR(300)"
},
{
"default": "False",
"name": "is_admin",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "BOOLEAN"
},
{
"default": "True",
"name": "is_active",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "BOOLEAN"
},
{
"default": {
"callable": "datetime.utcnow"
},
"name": "created_at",
"nullable": false,
"onupdate": null,
"primary_key": false,
"server_default": null,
"type": "DATETIME"
}
],
"constraints": [
{
"columns": [
"id"
],
"name": null,
"type": "PrimaryKeyConstraint"
},
{
"columns": [
"username"
],
"name": null,
"type": "UniqueConstraint"
}
],
"indexes": []
}
}
},
"workflows": {
"": {
"requests": [
{
"body": "{\"status\":\"ok\"}",
"headers": {
"content-type": "application/json"
},
"method": "GET",
"path": "/health",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Entrar · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n </nav>\n \n <div class=\"account\"><a href=\"/login\">Entrar</a></div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n<section class=\"auth-card\">\n <div class=\"auth-brand\" aria-hidden=\"true\">\n <img src=\"http://testserver/brand/assets/logo.png\" alt=\"\">\n </div>\n <h1>Entrar</h1>\n <p class=\"muted\">Use o seu utilizador e a sua palavra-passe da escala.</p>\n <form method=\"post\" action=\"/login\" class=\"stack\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <label>Utilizador\n <input name=\"username\" autocomplete=\"username\" required autofocus>\n </label>\n <label>Palavra-passe\n <input type=\"password\" name=\"password\" autocomplete=\"current-password\" required>\n </label>\n <button class=\"primary\" type=\"submit\">Entrar</button>\n </form>\n</section>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/login",
"status": 200
},
{
"body": "{\"detail\":\"É necessário iniciar sessão\"}",
"headers": {
"content-type": "application/json"
},
"method": "GET",
"path": "/account",
"status": 401
},
{
"body": "{\"detail\":\"É necessário iniciar sessão\"}",
"headers": {
"content-type": "application/json"
},
"method": "GET",
"path": "/admin/teams",
"status": 401
},
{
"body": "{\"detail\":\"É necessário iniciar sessão\"}",
"headers": {
"content-type": "application/json"
},
"method": "GET",
"path": "/admin/users",
"status": 401
},
{
"body": "{\"detail\":\"É necessário iniciar sessão\"}",
"headers": {
"content-type": "application/json"
},
"method": "GET",
"path": "/admin/schedules",
"status": 401
},
{
"body": "{\"detail\":\"É necessário iniciar sessão\"}",
"headers": {
"content-type": "application/json"
},
"method": "GET",
"path": "/swaps",
"status": 401
},
{
"body": "{\"detail\":\"Token CSRF inválido\"}",
"headers": {
"content-type": "application/json"
},
"method": "POST",
"path": "/login",
"status": 400
},
{
"body": "",
"headers": {
"location": "/"
},
"method": "POST",
"path": "/login",
"status": 303
},
{
"body": "",
"headers": {
"location": "/"
},
"method": "POST",
"path": "/login",
"status": 303
},
{
"body": "",
"headers": {
"location": "/"
},
"method": "POST",
"path": "/login",
"status": 303
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>A minha conta · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/swaps\">Trocas</a>\n <a href=\"/export/schedule\">Exportar PDF</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/account\" title=\"A minha conta\">\n <span class=\"name-text\">Membro a</span>\n <span class=\"muted\">@member-a</span>\n </a>\n <form method=\"post\" action=\"/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n <div class=\"flash info\" role=\"status\">Bem-vindo(a), Equipa Norte, Equipa partilhada.</div>\n \n \n<div class=\"page-head\">\n <div>\n <p class=\"eyebrow\"><a href=\"/\">Rota</a></p>\n <h1>A minha conta</h1>\n <p class=\"muted\">Membro a · @member-a</p>\n \n <p class=\"muted\">Este utilizador entra em nome de 2 equipa(s): Equipa Norte, Equipa partilhada. Vê e controla os turnos de todas.</p>\n \n </div>\n</div>\n\n<section class=\"panel\">\n <h2>Alterar palavra-passe</h2>\n <form method=\"post\" action=\"/account\" class=\"form-grid\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <input type=\"password\" name=\"new_password\" minlength=\"4\" autocomplete=\"new-password\" required autofocus>\n <div><button class=\"primary\" type=\"submit\">Alterar palavra-passe</button></div>\n </form>\n</section>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/account",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Calendário · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/swaps\">Trocas</a>\n <a href=\"/export/schedule\">Exportar PDF</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/account\" title=\"A minha conta\">\n <span class=\"name-text\">Membro a</span>\n <span class=\"muted\">@member-a</span>\n </a>\n <form method=\"post\" action=\"/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n<div class=\"page-head\">\n <div>\n <p class=\"eyebrow\"><a href=\"/\">Rota</a></p>\n <h1>Calendário</h1>\n <p class=\"muted\">Os teus turnos na app de calendário do telemóvel. Subscreve uma vez: as alterações à rota aparecem automaticamente, quando a app atualizar o calendário.</p>\n </div>\n</div>\n\n<div class=\"stack gap-lg\">\n\n <section class=\"panel calendar-feed\">\n <h2>Turnos de Equipa Norte</h2>\n <div class=\"calendar-subscribe-actions\" aria-label=\"Adicionar Turnos de Equipa Norte\">\n <a class=\"button primary\" href=\"webcal://testserver/calendar/phase4-feed-a.ics\">iPhone / Apple Calendar</a>\n <a class=\"button secondary\" href=\"https://calendar.google.com/calendar/render?cid=http%3A%2F%2Ftestserver%2Fcalendar%2Fphase4-feed-a.ics\" target=\"_blank\" rel=\"noopener noreferrer\">Google Calendar / Android</a>\n <a class=\"button secondary\" href=\"https://outlook.live.com/calendar/0/addfromweb?url=http%3A%2F%2Ftestserver%2Fcalendar%2Fphase4-feed-a.ics&amp;name=Turnos+de+Equipa+Norte\" target=\"_blank\" rel=\"noopener noreferrer\">Outlook</a>\n </div>\n <p class=\"muted\">No iPhone, confirma a subscrição na app Calendário. No Google e Outlook, adiciona pelo navegador à conta que usas no telemóvel. <a href=\"#calendar-help\">Ver os passos</a>.</p>\n <label for=\"calendar-url-1\">Endereço para subscrever noutra app</label>\n <div class=\"calendar-url-row\">\n <input id=\"calendar-url-1\" type=\"text\" readonly value=\"http://testserver/calendar/phase4-feed-a.ics\" inputmode=\"url\" autocapitalize=\"off\" spellcheck=\"false\">\n <button class=\"secondary\" type=\"button\" data-copy=\"http://testserver/calendar/phase4-feed-a.ics\">Copiar endereço</button>\n </div>\n <p class=\"calendar-copy-status muted\" role=\"status\" data-copy-status></p>\n </section>\n\n <section class=\"panel calendar-feed\">\n <h2>Turnos de Equipa partilhada</h2>\n <div class=\"calendar-subscribe-actions\" aria-label=\"Adicionar Turnos de Equipa partilhada\">\n <a class=\"button primary\" href=\"webcal://testserver/calendar/phase4-feed-c.ics\">iPhone / Apple Calendar</a>\n <a class=\"button secondary\" href=\"https://calendar.google.com/calendar/render?cid=http%3A%2F%2Ftestserver%2Fcalendar%2Fphase4-feed-c.ics\" target=\"_blank\" rel=\"noopener noreferrer\">Google Calendar / Android</a>\n <a class=\"button secondary\" href=\"https://outlook.live.com/calendar/0/addfromweb?url=http%3A%2F%2Ftestserver%2Fcalendar%2Fphase4-feed-c.ics&amp;name=Turnos+de+Equipa+partilhada\" target=\"_blank\" rel=\"noopener noreferrer\">Outlook</a>\n </div>\n <p class=\"muted\">No iPhone, confirma a subscrição na app Calendário. No Google e Outlook, adiciona pelo navegador à conta que usas no telemóvel. <a href=\"#calendar-help\">Ver os passos</a>.</p>\n <label for=\"calendar-url-2\">Endereço para subscrever noutra app</label>\n <div class=\"calendar-url-row\">\n <input id=\"calendar-url-2\" type=\"text\" readonly value=\"http://testserver/calendar/phase4-feed-c.ics\" inputmode=\"url\" autocapitalize=\"off\" spellcheck=\"false\">\n <button class=\"secondary\" type=\"button\" data-copy=\"http://testserver/calendar/phase4-feed-c.ics\">Copiar endereço</button>\n </div>\n <p class=\"calendar-copy-status muted\" role=\"status\" data-copy-status></p>\n </section>\n\n</div>\n\n<section class=\"panel calendar-help top-space\" id=\"calendar-help\">\n <h2>Como adicionar no telemóvel</h2>\n <details>\n <summary>iPhone / iPad — Apple Calendar</summary>\n <ol>\n <li>Toca em <strong>iPhone / Apple Calendar</strong> no calendário que queres adicionar.</li>\n <li>Permite abrir a app Calendário e confirma <strong>Subscrever</strong>.</li>\n <li>Se o botão não abrir a app, copia o endereço. Na app Calendário, abre <strong>Calendários → Adicionar calendário → Adicionar calendário de assinatura</strong> e cola-o.</li>\n </ol>\n </details>\n <details>\n <summary>Android / Google Calendar</summary>\n <ol>\n <li>Toca em <strong>Google Calendar / Android</strong> e entra na conta Google que usas no telemóvel.</li>\n <li>O Google exige a versão para computador para adicionar subscrições. No navegador do telemóvel, ativa <strong>Site para computador</strong>; se não conseguires, abre esta página num computador.</li>\n <li>Confirma a adição. Se não aparecer essa opção, copia o endereço e, no Google Calendar, escolhe <strong>Outros calendários → + → A partir de URL</strong>, cola-o e adiciona o calendário.</li>\n <li>Na app Google Calendar, abre o menu e ativa o novo calendário. Se não aparecer, verifica as definições de sincronização.</li>\n </ol>\n </details>\n <details>\n <summary>Outlook</summary>\n <ol>\n <li>Toca em <strong>Outlook</strong> e entra na conta que usas na app.</li>\n <li>Confirma a subscrição no Outlook na Web. Se necessário, usa <strong>Adicionar calendário → Subscrever a partir da Web</strong> e cola o endereço copiado.</li>\n <li>Na app Outlook, seleciona o novo calendário nessa conta. Se a versão móvel não mostrar a opção de subscrever, usa o navegador em modo computador.</li>\n </ol>\n </details>\n <p class=\"muted\">Escolhe uma subscrição por endereço, em vez de importar um ficheiro .ics, para receber futuras alterações. A atualização depende da app e pode demorar algumas horas. Para Google e Outlook, o endereço tem de estar acessível pela Internet.</p>\n</section>\n\n<script>\n // Clipboard permissions can fail even on HTTPS; keep a mobile-friendly fallback.\n document.querySelectorAll(\"[data-copy]\").forEach(function (button) {\n button.addEventListener(\"click\", async function () {\n var url = button.dataset.copy;\n var field = button.parentElement.querySelector(\"input\");\n var status = button.closest(\".calendar-feed\").querySelector(\"[data-copy-status]\");\n var copied = false;\n if (navigator.clipboard && window.isSecureContext) {\n try {\n await navigator.clipboard.writeText(url);\n copied = true;\n } catch (error) {\n // Fall through to selection/copy when clipboard access is denied.\n }\n }\n if (!copied) {\n field.focus();\n field.select();\n field.setSelectionRange(0, field.value.length);\n try {\n copied = document.execCommand(\"copy\");\n } catch (error) {\n // The selected field still supports the phone's native Copy action.\n }\n }\n if (copied) {\n button.textContent = \"Copiado\";\n status.textContent = \"Endereço copiado. Cola-o na tua app de calendário.\";\n setTimeout(function () { button.textContent = \"Copiar endereço\"; }, 2000);\n } else {\n status.textContent = \"Endereço selecionado. Toque no campo e escolha «Copiar».\";\n }\n });\n });\n</script>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/calendar",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/swaps\">Trocas</a>\n <a href=\"/export/schedule\">Exportar PDF</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/account\" title=\"A minha conta\">\n <span class=\"name-text\">Membro a</span>\n <span class=\"muted\">@member-a</span>\n </a>\n <form method=\"post\" action=\"/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n<div class=\"page-head\">\n <div><h1>Pedidos de troca</h1></div>\n</div>\n\n<div class=\"stack gap-lg\">\n\n <div class=\"empty\">Ainda não há pedidos de troca.</div>\n\n</div>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/swaps",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Pedir troca · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/swaps\">Trocas</a>\n <a href=\"/export/schedule\">Exportar PDF</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/account\" title=\"A minha conta\">\n <span class=\"name-text\">Membro a</span>\n <span class=\"muted\">@member-a</span>\n </a>\n <form method=\"post\" action=\"/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n<div class=\"page-head\">\n <div>\n <p class=\"eyebrow\"><a href=\"/swaps\">Trocas</a></p>\n <h1>Pedir troca</h1>\n <p class=\"muted\">Uma troca é entre duas equipas da mesma escala: cada uma fica com o turno da outra.</p>\n </div>\n <div class=\"month-nav\">\n <a class=\"button secondary\" href=\"/swaps/new?year=2029&amp;month=12\">← Anterior</a>\n <a class=\"button secondary\" href=\"/swaps/new?year=2030&amp;month=2\">Seguinte →</a>\n </div>\n</div>\n\n<div class=\"stack gap-lg\">\n\n <section class=\"panel\">\n <div class=\"row between wrap gap\">\n <h2>Rotação Phase 4</h2>\n <span class=\"muted\">Janeiro 2030</span>\n </div>\n <form method=\"post\" action=\"/swaps/new\" class=\"form-grid\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <label>O turno que passo\n <select name=\"assignment_id\" required>\n <option value=\"1\">sáb 05 jan · Equipa Norte</option><option value=\"3\">sáb 19 jan · Equipa Norte</option>\n </select>\n </label>\n <label>O turno que quero em troca\n <select name=\"target_assignment_id\" required>\n <option value=\"2\">sáb 12 jan · Equipa Sul</option><option value=\"4\">sáb 26 jan · Equipa Sul</option>\n </select>\n </label>\n <label>Mensagem <span class=\"muted\">(opcional)</span>\n <input name=\"message\" maxlength=\"500\" placeholder=\"Não consigo fazer este dia…\">\n </label>\n <div class=\"row end\"><button class=\"primary\" type=\"submit\">Enviar pedido</button></div>\n </form>\n </section>\n\n</div>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/swaps/new?year=2030&month=1",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/swaps\">Trocas</a>\n <a href=\"/export/schedule\">Exportar PDF</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/account\" title=\"A minha conta\">\n <span class=\"name-text\">Membro a</span>\n <span class=\"muted\">@member-a</span>\n </a>\n <form method=\"post\" action=\"/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n<div class=\"page-head\">\n <div>\n <h1>Janeiro 2030</h1>\n </div>\n <div class=\"month-nav\">\n \n <a class=\"button secondary\" href=\"/calendar\">Calendário</a>\n <a class=\"button primary\" href=\"/swaps/new?year=2030&amp;month=1\">Pedir troca</a>\n \n <a class=\"button secondary\" href=\"/?year=2029&amp;month=12\">← Anterior</a>\n <a class=\"button secondary\" href=\"/?year=2030&amp;month=2\">Seguinte →</a>\n </div>\n</div>\n\n<div class=\"calendar-list\">\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Ter</strong>\n <span>1</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Qua</strong>\n <span>2</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Qui</strong>\n <span>3</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Sex</strong>\n <span>4</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Sáb</strong>\n <span>5</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Almoço - Sábados</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment mine\">\n <div>\n <div class=\"assignment-title\">Rotação Phase 4</div>\n <div class=\"assignment-team\">\n Equipa Norte\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Dom</strong>\n <span>6</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Almoço - Domingo</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Seg</strong>\n <span>7</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Ter</strong>\n <span>8</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Qua</strong>\n <span>9</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Qui</strong>\n <span>10</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Sex</strong>\n <span>11</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Sáb</strong>\n <span>12</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Almoço - Sábados</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Rotação Phase 4</div>\n <div class=\"assignment-team\">\n Equipa Sul\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Dom</strong>\n <span>13</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Almoço - Domingo</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Seg</strong>\n <span>14</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Ter</strong>\n <span>15</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Qua</strong>\n <span>16</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Qui</strong>\n <span>17</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Sex</strong>\n <span>18</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Sáb</strong>\n <span>19</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Almoço - Sábados</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment mine\">\n <div>\n <div class=\"assignment-title\">Rotação Phase 4</div>\n <div class=\"assignment-team\">\n Equipa Norte\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Dom</strong>\n <span>20</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Almoço - Domingo</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Seg</strong>\n <span>21</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Ter</strong>\n <span>22</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Qua</strong>\n <span>23</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Qui</strong>\n <span>24</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Sex</strong>\n <span>25</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Sáb</strong>\n <span>26</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Almoço - Sábados</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Rotação Phase 4</div>\n <div class=\"assignment-team\">\n Equipa Sul\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Dom</strong>\n <span>27</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Almoço - Domingo</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Seg</strong>\n <span>28</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Ter</strong>\n <span>29</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Qua</strong>\n <span>30</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Qui</strong>\n <span>31</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n</div>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/?year=2030&month=1",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Exportar escala · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/swaps\">Trocas</a>\n <a href=\"/export/schedule\">Exportar PDF</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/account\" title=\"A minha conta\">\n <span class=\"name-text\">Membro a</span>\n <span class=\"muted\">@member-a</span>\n </a>\n <form method=\"post\" action=\"/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n<div class=\"page-head\">\n <div>\n <p class=\"eyebrow\"><a href=\"/\">Rota</a></p>\n <h1>Exportar escala</h1>\n <p class=\"muted\">Escolhe uma escala e as datas. O PDF é descarregado numa única folha A4, com os dias e as equipas agrupados por mês.</p>\n </div>\n</div>\n<section class=\"panel\">\n \n<form class=\"stack\" method=\"get\" action=\"/export/schedule.pdf\">\n <label>Escala\n <select name=\"schedule_id\" required>\n \n <option value=\"1\" selected>Noite</option>\n \n <option value=\"2\" >Almoço - Sábados</option>\n \n <option value=\"3\" >Almoço - Domingo</option>\n \n <option value=\"4\" >Rotação Phase 4</option>\n \n </select>\n </label>\n <div class=\"form-grid\">\n <label>Data inicial\n <input type=\"date\" name=\"start_date\" value=\"2026-10-01\" min=\"2000-01-01\" max=\"2100-12-31\" required>\n </label>\n <label>Data final\n <input type=\"date\" name=\"end_date\" value=\"2026-10-31\" min=\"2000-01-01\" max=\"2100-12-31\" required>\n </label>\n </div>\n <p class=\"muted\">As datas inicial e final estão incluídas. Máximo de 12 meses; o tamanho do texto adapta-se ao intervalo.</p>\n <div class=\"row end\"><button class=\"primary\" type=\"submit\">Descarregar PDF</button></div>\n</form>\n</section>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/export/schedule",
"status": 200
},
{
"body": "{\"detail\":\"É necessária a permissão de administrador\"}",
"headers": {
"content-type": "application/json"
},
"method": "GET",
"path": "/admin/users",
"status": 403
},
{
"body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Contínuos CRR//Rota//PT\r\nCALSCALE:GREGORIAN\r\nMETHOD:PUBLISH\r\nX-WR-CALNAME:Contínuos CRR — Equipa Norte\r\nX-WR-CALDESC:Contínuos CRR — 1 de agosto de 2026 a 31 de março de 2027\r\nREFRESH-INTERVAL;VALUE=DURATION:PT12H\r\nX-PUBLISHED-TTL:PT12H\r\nX-WR-TIMEZONE:Europe/Lisbon\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Lisbon\r\nX-LIC-LOCATION:Europe/Lisbon\r\nBEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0000\r\nTZOFFSETTO:+0100\r\nTZNAME:WEST\r\nDTSTART:19700329T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0000\r\nTZNAME:WET\r\nDTSTART:19701025T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nEND:VCALENDAR\r\n",
"headers": {
"cache-control": "private, max-age=300",
"content-disposition": "inline; filename=\"rota.ics\"",
"content-type": "text/calendar; charset=utf-8"
},
"method": "GET",
"path": "/calendar/phase4-feed-a.ics",
"status": 200
},
{
"body": "{\"detail\":\"Calendário não encontrado\"}",
"headers": {
"content-type": "application/json"
},
"method": "GET",
"path": "/calendar/not-a-token.ics",
"status": 404
},
{
"body": "CONTÍNUOS CRR\nRotação Phase 4\nJaneiro 2030\n 5\nEquipa Norte\n12\nEquipa Sul\n19\nEquipa Norte\n26\nEquipa Sul\n",
"headers": {
"cache-control": "private, no-store",
"content-disposition": "attachment; filename=\"escala-4-2030-01-01-2030-01-31.pdf\"",
"content-type": "application/pdf"
},
"method": "GET",
"path": "/export/schedule.pdf?schedule_id=4&start_date=2030-01-01&end_date=2030-01-31",
"status": 200
},
{
"body": "",
"headers": {
"location": "/swaps"
},
"method": "POST",
"path": "/assignments/1/swap",
"status": 303
},
{
"body": "",
"headers": {
"location": "/swaps"
},
"method": "POST",
"path": "/swaps/1/accept",
"status": 303
},
{
"body": "",
"headers": {
"location": "/swaps"
},
"method": "POST",
"path": "/swaps/1/approve",
"status": 303
},
{
"body": "",
"headers": {
"location": "/swaps"
},
"method": "POST",
"path": "/swaps/1/revert",
"status": 303
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Equipas · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/swaps\">Trocas</a>\n \n \n <a href=\"/admin/schedules\">Gerir escalas</a>\n <a href=\"/admin/teams\">Equipas</a>\n <a href=\"/admin/users\">Utilizadores</a>\n <a href=\"/admin/notifications\">Registo de e-mails</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/account\" title=\"A minha conta\">\n <span class=\"name-text\">Administrador de teste</span>\n <span class=\"muted\">@phase4-admin</span>\n </a>\n <form method=\"post\" action=\"/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n <div class=\"flash info\" role=\"status\">Troca revertida: os dois turnos voltaram a quem os tinha.</div>\n \n \n<div class=\"page-head\">\n <div>\n <h1>Equipas</h1>\n <p class=\"muted\">Quem entra na rota. Uma equipa é quem fica com um turno, e pode ser uma pessoa ou várias. Os utilizadores que entram na aplicação criam-se em <a href=\"/admin/users\">Utilizadores</a>; aqui escolhe-se apenas quem entra para cada equipa.</p>\n </div>\n</div>\n<section class=\"panel\">\n <div class=\"row between wrap gap\">\n <h2>Adicionar equipa</h2>\n <button class=\"primary\" type=\"submit\" form=\"add-team\">Adicionar equipa</button>\n </div>\n <form id=\"add-team\" method=\"post\" action=\"/admin/teams\" class=\"form-grid\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <div class=\"form-col\">\n <label>Nome<input name=\"name\" required></label>\n <label>Telefone<input name=\"phone\"></label>\n <label>E-mail<input type=\"email\" name=\"email\"></label>\n </div>\n <fieldset class=\"form-col\">\n <legend>Utilizador <span class=\"muted\">(opcional)</span></legend>\n <label>Entra com\n <select name=\"assigned_user_id\">\n <option value=\"\">Ninguém — só aparece na escala</option>\n <option value=\"2\">@member-a — Membro a</option><option value=\"3\">@member-b — Membro b</option><option value=\"1\">@phase4-admin — Administrador de teste</option>\n </select>\n </label>\n </fieldset>\n <label class=\"check\"><input type=\"checkbox\" name=\"notify_email\" checked> Notificações por e-mail</label>\n </form>\n</section>\n<div class=\"stack gap-lg top-space\">\n\n<details class=\"panel\">\n <summary><strong>Administrador de teste</strong> <span class=\"muted\">@phase4-admin</span></summary>\n <form method=\"post\" action=\"/admin/teams/1/update\" class=\"form-grid top-space\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <div class=\"form-col\">\n <label>Nome<input name=\"name\" value=\"Administrador de teste\" required></label>\n <label>Telefone<input name=\"phone\" value=\"\"></label>\n <label>E-mail<input type=\"email\" name=\"email\" value=\"admin@example.com\"></label>\n </div>\n <fieldset class=\"form-col\">\n <legend>Utilizador</legend>\n <label>Entra com\n <select name=\"assigned_user_id\">\n <option value=\"\">Ninguém — só aparece na escala</option>\n <option value=\"2\" >@member-a — Membro a</option><option value=\"3\" >@member-b — Membro b</option><option value=\"1\" selected>@phase4-admin — Administrador de teste</option>\n </select>\n </label>\n <p class=\"muted\">O mesmo utilizador pode entrar em várias equipas: vê e controla os turnos de todas elas.</p>\n </fieldset>\n <label class=\"check\"><input type=\"checkbox\" name=\"notify_email\" checked> Notificações por e-mail</label>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_active\" checked> Ativa</label>\n <div><button class=\"primary\" type=\"submit\">Guardar</button></div>\n \n </form>\n</details>\n\n<details class=\"panel\">\n <summary><strong>Equipa Norte</strong> <span class=\"muted\">@member-a · partilhada com 1</span></summary>\n <form method=\"post\" action=\"/admin/teams/2/update\" class=\"form-grid top-space\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <div class=\"form-col\">\n <label>Nome<input name=\"name\" value=\"Equipa Norte\" required></label>\n <label>Telefone<input name=\"phone\" value=\"\"></label>\n <label>E-mail<input type=\"email\" name=\"email\" value=\"a@example.com\"></label>\n </div>\n <fieldset class=\"form-col\">\n <legend>Utilizador</legend>\n <label>Entra com\n <select name=\"assigned_user_id\">\n <option value=\"\">Ninguém — só aparece na escala</option>\n <option value=\"2\" selected>@member-a — Membro a</option><option value=\"3\" >@member-b — Membro b</option><option value=\"1\" >@phase4-admin — Administrador de teste</option>\n </select>\n </label>\n <p class=\"muted\">O mesmo utilizador pode entrar em várias equipas: vê e controla os turnos de todas elas.</p>\n </fieldset>\n <label class=\"check\"><input type=\"checkbox\" name=\"notify_email\" checked> Notificações por e-mail</label>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_active\" checked> Ativa</label>\n <div><button class=\"primary\" type=\"submit\">Guardar</button></div>\n \n </form>\n</details>\n\n<details class=\"panel\">\n <summary><strong>Equipa Sul</strong> <span class=\"muted\">@member-b</span></summary>\n <form method=\"post\" action=\"/admin/teams/3/update\" class=\"form-grid top-space\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <div class=\"form-col\">\n <label>Nome<input name=\"name\" value=\"Equipa Sul\" required></label>\n <label>Telefone<input name=\"phone\" value=\"\"></label>\n <label>E-mail<input type=\"email\" name=\"email\" value=\"b@example.com\"></label>\n </div>\n <fieldset class=\"form-col\">\n <legend>Utilizador</legend>\n <label>Entra com\n <select name=\"assigned_user_id\">\n <option value=\"\">Ninguém — só aparece na escala</option>\n <option value=\"2\" >@member-a — Membro a</option><option value=\"3\" selected>@member-b — Membro b</option><option value=\"1\" >@phase4-admin — Administrador de teste</option>\n </select>\n </label>\n <p class=\"muted\">O mesmo utilizador pode entrar em várias equipas: vê e controla os turnos de todas elas.</p>\n </fieldset>\n <label class=\"check\"><input type=\"checkbox\" name=\"notify_email\" checked> Notificações por e-mail</label>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_active\" checked> Ativa</label>\n <div><button class=\"primary\" type=\"submit\">Guardar</button></div>\n \n </form>\n</details>\n\n<details class=\"panel\">\n <summary><strong>Equipa partilhada</strong> <span class=\"muted\">@member-a · partilhada com 1</span></summary>\n <form method=\"post\" action=\"/admin/teams/4/update\" class=\"form-grid top-space\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <div class=\"form-col\">\n <label>Nome<input name=\"name\" value=\"Equipa partilhada\" required></label>\n <label>Telefone<input name=\"phone\" value=\"\"></label>\n <label>E-mail<input type=\"email\" name=\"email\" value=\"c@example.com\"></label>\n </div>\n <fieldset class=\"form-col\">\n <legend>Utilizador</legend>\n <label>Entra com\n <select name=\"assigned_user_id\">\n <option value=\"\">Ninguém — só aparece na escala</option>\n <option value=\"2\" selected>@member-a — Membro a</option><option value=\"3\" >@member-b — Membro b</option><option value=\"1\" >@phase4-admin — Administrador de teste</option>\n </select>\n </label>\n <p class=\"muted\">O mesmo utilizador pode entrar em várias equipas: vê e controla os turnos de todas elas.</p>\n </fieldset>\n <label class=\"check\"><input type=\"checkbox\" name=\"notify_email\" checked> Notificações por e-mail</label>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_active\" checked> Ativa</label>\n <div><button class=\"primary\" type=\"submit\">Guardar</button></div>\n <div><a class=\"button danger\" href=\"/admin/teams/4/delete\">Remover</a></div>\n </form>\n</details>\n\n</div>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/admin/teams",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Utilizadores · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/swaps\">Trocas</a>\n \n \n <a href=\"/admin/schedules\">Gerir escalas</a>\n <a href=\"/admin/teams\">Equipas</a>\n <a href=\"/admin/users\">Utilizadores</a>\n <a href=\"/admin/notifications\">Registo de e-mails</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/account\" title=\"A minha conta\">\n <span class=\"name-text\">Administrador de teste</span>\n <span class=\"muted\">@phase4-admin</span>\n </a>\n <form method=\"post\" action=\"/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n<div class=\"page-head\">\n <div>\n <h1>Utilizadores</h1>\n <p class=\"muted\">Cada utilizador tem utilizador e palavra-passe e pode entrar em várias equipas. Quem entra vê e controla os turnos de todas elas, e a administração é do utilizador, não da equipa.</p>\n </div>\n</div>\n<section class=\"panel\">\n <div class=\"row between wrap gap\">\n <h2>Adicionar utilizador</h2>\n <button class=\"primary\" type=\"submit\" form=\"add-user\">Adicionar utilizador</button>\n </div>\n <form id=\"add-user\" method=\"post\" action=\"/admin/users\" class=\"form-grid\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <div class=\"form-col\">\n <label>Nome <span class=\"muted\">(ex.: Equipa de sábado)</span><input name=\"name\" required></label>\n <label>Utilizador<input name=\"username\" pattern=\"[A-Za-z0-9._-]+\" required></label>\n <label>Palavra-passe<input type=\"password\" name=\"password\" minlength=\"4\" required></label>\n </div>\n <fieldset class=\"form-col\">\n <legend>Equipas <span class=\"muted\">(pode escolher várias)</span></legend>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"1\"> Administrador de teste</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"2\"> Equipa Norte</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"3\"> Equipa Sul</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"4\"> Equipa partilhada</label>\n \n </fieldset>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_admin\"> Administrador</label>\n </form>\n</section>\n<div class=\"stack gap-lg top-space\">\n\n<details class=\"panel\">\n <summary><strong>@member-a</strong> <span class=\"muted\">Membro a · 2 equipa(s)</span></summary>\n <form method=\"post\" action=\"/admin/users/2/update\" class=\"form-grid top-space\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <div class=\"form-col\">\n <label>Nome<input name=\"name\" value=\"Membro a\" required></label>\n <label>Utilizador<input name=\"username\" pattern=\"[A-Za-z0-9._-]+\" value=\"member-a\" required></label>\n <label>Nova palavra-passe <span class=\"muted\">(vazio mantém a atual)</span><input type=\"password\" name=\"password\" minlength=\"4\"></label>\n </div>\n <fieldset class=\"form-col\">\n <legend>Equipas que entra</legend>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"1\" > Administrador de teste</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"2\" checked> Equipa Norte</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"3\" > Equipa Sul</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"4\" checked> Equipa partilhada</label>\n \n </fieldset>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_admin\" > Administrador</label>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_active\" checked> Ativo</label>\n <div><button class=\"primary\" type=\"submit\">Guardar</button></div>\n \n </form>\n \n <p class=\"muted\">Equipas: Equipa Norte, Equipa partilhada</p>\n \n</details>\n\n<details class=\"panel\">\n <summary><strong>@member-b</strong> <span class=\"muted\">Membro b · 1 equipa(s)</span></summary>\n <form method=\"post\" action=\"/admin/users/3/update\" class=\"form-grid top-space\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <div class=\"form-col\">\n <label>Nome<input name=\"name\" value=\"Membro b\" required></label>\n <label>Utilizador<input name=\"username\" pattern=\"[A-Za-z0-9._-]+\" value=\"member-b\" required></label>\n <label>Nova palavra-passe <span class=\"muted\">(vazio mantém a atual)</span><input type=\"password\" name=\"password\" minlength=\"4\"></label>\n </div>\n <fieldset class=\"form-col\">\n <legend>Equipas que entra</legend>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"1\" > Administrador de teste</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"2\" > Equipa Norte</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"3\" checked> Equipa Sul</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"4\" > Equipa partilhada</label>\n \n </fieldset>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_admin\" > Administrador</label>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_active\" checked> Ativo</label>\n <div><button class=\"primary\" type=\"submit\">Guardar</button></div>\n \n </form>\n \n <p class=\"muted\">Equipas: Equipa Sul</p>\n \n</details>\n\n<details class=\"panel\">\n <summary><strong>@phase4-admin</strong> <span class=\"muted\">Administrador de teste · administrador · 1 equipa(s)</span></summary>\n <form method=\"post\" action=\"/admin/users/1/update\" class=\"form-grid top-space\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <div class=\"form-col\">\n <label>Nome<input name=\"name\" value=\"Administrador de teste\" required></label>\n <label>Utilizador<input name=\"username\" pattern=\"[A-Za-z0-9._-]+\" value=\"phase4-admin\" required></label>\n <label>Nova palavra-passe <span class=\"muted\">(vazio mantém a atual)</span><input type=\"password\" name=\"password\" minlength=\"4\"></label>\n </div>\n <fieldset class=\"form-col\">\n <legend>Equipas que entra</legend>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"1\" checked> Administrador de teste</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"2\" > Equipa Norte</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"3\" > Equipa Sul</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"4\" > Equipa partilhada</label>\n \n </fieldset>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_admin\" checked> Administrador</label>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_active\" checked> Ativo</label>\n <div><button class=\"primary\" type=\"submit\">Guardar</button></div>\n \n </form>\n \n <p class=\"muted\">Equipas: Administrador de teste</p>\n \n</details>\n\n</div>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/admin/users",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/swaps\">Trocas</a>\n \n \n <a href=\"/admin/schedules\">Gerir escalas</a>\n <a href=\"/admin/teams\">Equipas</a>\n <a href=\"/admin/users\">Utilizadores</a>\n <a href=\"/admin/notifications\">Registo de e-mails</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/account\" title=\"A minha conta\">\n <span class=\"name-text\">Administrador de teste</span>\n <span class=\"muted\">@phase4-admin</span>\n </a>\n <form method=\"post\" action=\"/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n<div class=\"page-head\">\n <div>\n <h1>Escalas</h1>\n <p class=\"muted\">Uma página por tipo de escala. A escala da noite é fixa e repete todos os meses; o almoço de sábado e o de domingo são rotações independentes.</p>\n </div>\n <a class=\"button primary\" href=\"/admin/assign\">Atribuir por dia</a>\n</div>\n<div class=\"stack gap\">\n\n<section class=\"panel\" id=\"export-pdf\">\n <h2>Exportar PDF</h2>\n <p class=\"muted\">Escolhe uma escala e as datas para descarregar a lista de dias e equipas numa única folha A4.</p>\n \n<form class=\"stack\" method=\"get\" action=\"/export/schedule.pdf\">\n <label>Escala\n <select name=\"schedule_id\" required>\n \n <option value=\"1\" selected>Noite</option>\n \n <option value=\"2\" >Almoço - Sábados</option>\n \n <option value=\"3\" >Almoço - Domingo</option>\n \n <option value=\"4\" >Rotação Phase 4</option>\n \n </select>\n </label>\n <div class=\"form-grid\">\n <label>Data inicial\n <input type=\"date\" name=\"start_date\" value=\"2026-10-01\" min=\"2000-01-01\" max=\"2100-12-31\" required>\n </label>\n <label>Data final\n <input type=\"date\" name=\"end_date\" value=\"2026-10-31\" min=\"2000-01-01\" max=\"2100-12-31\" required>\n </label>\n </div>\n <p class=\"muted\">As datas inicial e final estão incluídas. Máximo de 12 meses; o tamanho do texto adapta-se ao intervalo.</p>\n <div class=\"row end\"><button class=\"primary\" type=\"submit\">Descarregar PDF</button></div>\n</form>\n</section>\n\n\n\n<section class=\"panel\">\n <div class=\"row between wrap gap\">\n <div>\n <h2>Noite</h2>\n <p class=\"muted\">Todos os dias · repete todos os meses · 20:30–00:00</p>\n </div>\n <div class=\"actions\">\n <a class=\"button secondary\" href=\"/admin/schedules?schedule_id=1#export-pdf\">Exportar PDF</a>\n <a class=\"button primary\" href=\"/admin/schedules/1\">Configurar</a>\n </div>\n </div>\n \n <h3>Padrão</h3>\n <ul class=\"link-list\">\n <li class=\"muted\">Ainda não há ninguém definido.</li>\n </ul>\n \n</section>\n\n\n<section class=\"panel\">\n <div class=\"row between wrap gap\">\n <div>\n <h2>Almoço - Sábados</h2>\n <p class=\"muted\">Rotação · Sábado · 13:00–16:00</p>\n </div>\n <div class=\"actions\">\n <a class=\"button secondary\" href=\"/admin/schedules?schedule_id=2#export-pdf\">Exportar PDF</a>\n <a class=\"button primary\" href=\"/admin/schedules/2\">Configurar</a>\n </div>\n </div>\n \n <p class=\"muted\">Próximas datas</p>\n <p class=\"date-list\">sáb 10 out · sáb 17 out · sáb 24 out · sáb 31 out · sáb 07 nov · sáb 14 nov</p>\n <h3>Ordem da rotação</h3>\n <ol class=\"rotation-list\">\n <li class=\"muted\">Ainda não há membros.</li>\n </ol>\n \n</section>\n\n\n<section class=\"panel\">\n <div class=\"row between wrap gap\">\n <div>\n <h2>Almoço - Domingo</h2>\n <p class=\"muted\">Rotação · Domingo · 13:00–16:00</p>\n </div>\n <div class=\"actions\">\n <a class=\"button secondary\" href=\"/admin/schedules?schedule_id=3#export-pdf\">Exportar PDF</a>\n <a class=\"button primary\" href=\"/admin/schedules/3\">Configurar</a>\n </div>\n </div>\n \n <p class=\"muted\">Próximas datas</p>\n <p class=\"date-list\">dom 11 out · dom 18 out · dom 25 out · dom 01 nov · dom 08 nov · dom 15 nov</p>\n <h3>Ordem da rotação</h3>\n <ol class=\"rotation-list\">\n <li class=\"muted\">Ainda não há membros.</li>\n </ol>\n \n</section>\n\n\n<section class=\"panel\">\n <div class=\"row between wrap gap\">\n <div>\n <h2>Rotação Phase 4</h2>\n <p class=\"muted\">Rotação · Sábado · 12:00–14:00</p>\n </div>\n <div class=\"actions\">\n <a class=\"button secondary\" href=\"/admin/schedules?schedule_id=4#export-pdf\">Exportar PDF</a>\n <a class=\"button primary\" href=\"/admin/schedules/4\">Configurar</a>\n </div>\n </div>\n \n <p class=\"muted\">Próximas datas</p>\n <p class=\"date-list\">sáb 10 out · sáb 17 out · sáb 24 out · sáb 31 out · sáb 07 nov · sáb 14 nov</p>\n <h3>Ordem da rotação</h3>\n <ol class=\"rotation-list\">\n \n <li><span>Equipa Norte</span></li>\n \n <li><span>Equipa Sul</span></li>\n \n </ol>\n \n</section>\n\n<section class=\"panel\">\n <h2>Calendário da equipa</h2>\n <p class=\"muted\">Um único endereço com a rota completa, para qualquer calendário.</p>\n <p><a class=\"button secondary\" href=\"/calendar\">Adicionar ao calendário do telemóvel</a></p>\n <div class=\"inline-form\">\n <input type=\"text\" readonly value=\"http://testserver/calendar/phase4-whole-rota.ics\" aria-label=\"Endereço do calendário da equipa\">\n <button class=\"secondary\" type=\"button\" data-copy=\"http://testserver/calendar/phase4-whole-rota.ics\">Copiar</button>\n </div>\n</section>\n</div>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/admin/schedules",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/swaps\">Trocas</a>\n \n \n <a href=\"/admin/schedules\">Gerir escalas</a>\n <a href=\"/admin/teams\">Equipas</a>\n <a href=\"/admin/users\">Utilizadores</a>\n <a href=\"/admin/notifications\">Registo de e-mails</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/account\" title=\"A minha conta\">\n <span class=\"name-text\">Administrador de teste</span>\n <span class=\"muted\">@phase4-admin</span>\n </a>\n <form method=\"post\" action=\"/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n\n<div class=\"page-head\">\n <div>\n <p class=\"eyebrow\"><a href=\"/admin/schedules\">Escalas</a></p>\n <h1>Rotação Phase 4</h1>\n <p class=\"muted\">Rotação · Sábado · 12:00–14:00</p>\n </div>\n <form method=\"post\" action=\"/admin/schedules/4/settings\" class=\"inline-form\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <label>Das horas<input type=\"time\" name=\"start_time\" value=\"12:00\" required></label>\n <label>Às horas<input type=\"time\" name=\"end_time\" value=\"14:00\" required></label>\n <label class=\"check\"><input type=\"checkbox\" name=\"requires_manager_approval\" checked> Aprovação da gestão</label>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_active\" checked> Ativa</label>\n <button class=\"secondary\" type=\"submit\">Guardar</button>\n </form>\n</div>\n<div class=\"stack gap-lg\">\n\n <section class=\"panel\">\n <div class=\"row between wrap gap\">\n <div><h3>Janeiro 2030</h3></div>\n <div class=\"month-nav\">\n <a class=\"button secondary\" href=\"/admin/schedules/4?year=2029&amp;month=12\">← Anterior</a>\n <a class=\"button secondary\" href=\"/admin/schedules/4?year=2030&amp;month=2\">Seguinte →</a>\n </div>\n </div>\n <form method=\"post\" action=\"/admin/schedules/4/apply-rotation\" class=\"inline-form\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <input type=\"hidden\" name=\"year\" value=\"2030\"><input type=\"hidden\" name=\"month\" value=\"1\">\n <button class=\"secondary\" type=\"submit\">Aplicar rotação em Janeiro</button>\n </form>\n <p class=\"muted\">Deixa sem atribuição os turnos <strong>automáticos</strong> do mês e ano escolhidos e de todos os seguintes. Nenhum turno é apagado e as atribuições manuais e as trocas concluídas são mantidas. Use Limpar turnos antes de Aplicar rotação, para recalcular tudo a partir de um ponto.</p>\n <form method=\"post\" action=\"/admin/schedules/4/assignments\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <input type=\"hidden\" name=\"year\" value=\"2030\"><input type=\"hidden\" name=\"month\" value=\"1\">\n <div class=\"table-wrap\"><table><thead><tr><th>Data</th><th>Equipa</th><th>Origem</th><th>Ação</th></tr></thead><tbody>\n \n <tr><td>sáb 05 jan</td><td><select name=\"assignment_1\"><option value=\"\">Sem atribuição</option><option value=\"1\" >Administrador de teste</option><option value=\"2\" selected>Equipa Norte</option><option value=\"3\" >Equipa Sul</option><option value=\"4\" >Equipa partilhada</option></select></td><td><span class=\"badge\">manual</span></td><td><button class=\"secondary small\" type=\"submit\" form=\"regenerate-1\" aria-label=\"Recalcular o turno de sáb 05 jan\">Recalcular</button></td></tr>\n \n <tr><td>sáb 12 jan</td><td><select name=\"assignment_2\"><option value=\"\">Sem atribuição</option><option value=\"1\" >Administrador de teste</option><option value=\"2\" >Equipa Norte</option><option value=\"3\" selected>Equipa Sul</option><option value=\"4\" >Equipa partilhada</option></select></td><td><span class=\"badge\">manual</span></td><td><button class=\"secondary small\" type=\"submit\" form=\"regenerate-2\" aria-label=\"Recalcular o turno de sáb 12 jan\">Recalcular</button></td></tr>\n \n <tr><td>sáb 19 jan</td><td><select name=\"assignment_3\"><option value=\"\">Sem atribuição</option><option value=\"1\" >Administrador de teste</option><option value=\"2\" selected>Equipa Norte</option><option value=\"3\" >Equipa Sul</option><option value=\"4\" >Equipa partilhada</option></select></td><td><span class=\"badge\">troca</span></td><td><button class=\"secondary small\" type=\"submit\" form=\"regenerate-3\" aria-label=\"Recalcular o turno de sáb 19 jan\">Recalcular</button></td></tr>\n \n <tr><td>sáb 26 jan</td><td><select name=\"assignment_4\"><option value=\"\">Sem atribuição</option><option value=\"1\" >Administrador de teste</option><option value=\"2\" >Equipa Norte</option><option value=\"3\" selected>Equipa Sul</option><option value=\"4\" >Equipa partilhada</option></select></td><td><span class=\"badge\">automático</span></td><td><button class=\"secondary small\" type=\"submit\" form=\"regenerate-4\" aria-label=\"Recalcular o turno de sáb 26 jan\">Recalcular</button></td></tr>\n \n </tbody></table></div>\n <div class=\"sticky-actions\"><button class=\"primary\" type=\"submit\">Guardar Janeiro</button></div>\n </form>\n \n <form id=\"regenerate-1\" method=\"post\" action=\"/admin/schedules/4/assignments/1/regenerate\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n </form>\n \n <form id=\"regenerate-2\" method=\"post\" action=\"/admin/schedules/4/assignments/2/regenerate\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n </form>\n \n <form id=\"regenerate-3\" method=\"post\" action=\"/admin/schedules/4/assignments/3/regenerate\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n </form>\n \n <form id=\"regenerate-4\" method=\"post\" action=\"/admin/schedules/4/assignments/4/regenerate\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n </form>\n \n </section>\n <section class=\"panel\">\n <h3>Próximas datas</h3>\n <p class=\"date-list\">sáb 10 out · sáb 17 out · sáb 24 out · sáb 31 out · sáb 07 nov · sáb 14 nov</p>\n </section>\n <section class=\"panel\" id=\"rotation-order\">\n <h3>Ordem da rotação</h3>\n <p class=\"muted\">Rotação independente, preenchida por ordem. Depois de alterar a ordem, use Aplicar rotação acima para atualizar os turnos automáticos deste mês.</p>\n <ol class=\"rotation-list\">\n \n <li>\n <span>Equipa Norte</span>\n <div class=\"actions compact\">\n <form method=\"post\" action=\"/admin/rotation/1/move\"><input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\"><input type=\"hidden\" name=\"direction\" value=\"up\"><button class=\"secondary small\" type=\"submit\" aria-label=\"Subir Equipa Norte\">↑</button></form>\n <form method=\"post\" action=\"/admin/rotation/1/move\"><input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\"><input type=\"hidden\" name=\"direction\" value=\"down\"><button class=\"secondary small\" type=\"submit\" aria-label=\"Descer Equipa Norte\">↓</button></form>\n <form method=\"post\" action=\"/admin/rotation/1/remove\"><input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\"><button class=\"danger small\" type=\"submit\">Remover</button></form>\n </div>\n </li>\n \n <li>\n <span>Equipa Sul</span>\n <div class=\"actions compact\">\n <form method=\"post\" action=\"/admin/rotation/2/move\"><input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\"><input type=\"hidden\" name=\"direction\" value=\"up\"><button class=\"secondary small\" type=\"submit\" aria-label=\"Subir Equipa Sul\">↑</button></form>\n <form method=\"post\" action=\"/admin/rotation/2/move\"><input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\"><input type=\"hidden\" name=\"direction\" value=\"down\"><button class=\"secondary small\" type=\"submit\" aria-label=\"Descer Equipa Sul\">↓</button></form>\n <form method=\"post\" action=\"/admin/rotation/2/remove\"><input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\"><button class=\"danger small\" type=\"submit\">Remover</button></form>\n </div>\n </li>\n \n </ol>\n <form method=\"post\" action=\"/admin/schedules/4/rotation\" class=\"inline-form\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <select name=\"team_id\" required><option value=\"\">Adicionar equipa…</option><option value=\"1\">Administrador de teste</option><option value=\"2\">Equipa Norte</option><option value=\"3\">Equipa Sul</option><option value=\"4\">Equipa partilhada</option></select>\n <button class=\"primary\" type=\"submit\">Adicionar à rotação</button>\n </form>\n </section>\n\n <section class=\"panel\">\n <h3>Limpar atribuições</h3>\n <p class=\"muted\">Limpar atribuições a partir do mês de:</p>\n <form method=\"post\" action=\"/admin/schedules/4/clear-from\" class=\"inline-form\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <label><select name=\"month\">\n <option value=\"1\" selected>Janeiro</option><option value=\"2\" >Fevereiro</option><option value=\"3\" >Março</option><option value=\"4\" >Abril</option><option value=\"5\" >Maio</option><option value=\"6\" >Junho</option><option value=\"7\" >Julho</option><option value=\"8\" >Agosto</option><option value=\"9\" >Setembro</option><option value=\"10\" >Outubro</option><option value=\"11\" >Novembro</option><option value=\"12\" >Dezembro</option>\n </select></label>\n <label><input type=\"number\" name=\"year\" value=\"2030\" min=\"2000\" max=\"2100\" required></label>\n <button class=\"danger\" type=\"submit\">Limpar turnos</button>\n </form>\n </section>\n <section class=\"panel\" id=\"swap-requests\">\n <h3>Pedidos de troca</h3>\n <p class=\"muted\">Um administrador pode atribuir o turno a outra equipa, mesmo que nenhuma pessoa entre na aplicação por ela. Só se aplica se quem pediu a troca continuar a ser o titular do turno.</p>\n <ul class=\"link-list\">\n \n <li class=\"muted\">Não há pedidos de troca nesta escala.</li>\n \n </ul>\n </section>\n</div>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/admin/schedules/4?year=2030&month=1",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Atribuição por dia · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/swaps\">Trocas</a>\n \n \n <a href=\"/admin/schedules\">Gerir escalas</a>\n <a href=\"/admin/teams\">Equipas</a>\n <a href=\"/admin/users\">Utilizadores</a>\n <a href=\"/admin/notifications\">Registo de e-mails</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/account\" title=\"A minha conta\">\n <span class=\"name-text\">Administrador de teste</span>\n <span class=\"muted\">@phase4-admin</span>\n </a>\n <form method=\"post\" action=\"/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n<div class=\"page-head\">\n <div>\n <p class=\"eyebrow\"><a href=\"/admin/schedules\">Escalas</a></p>\n <h1>Atribuição por dia</h1>\n <p class=\"muted\">Escolha o dia e a equipa que fica com cada turno. É uma atribuição direta, não uma troca.</p>\n </div>\n <form method=\"get\" action=\"/admin/assign\" class=\"inline-form\">\n <label>Dia<input type=\"date\" name=\"on\" value=\"2030-01-05\" min=\"1999-12-31\" max=\"2100-12-30\" required onchange=\"this.form.submit()\"></label>\n </form>\n</div>\n\n<div class=\"month-nav\">\n <a class=\"button secondary\" href=\"/admin/assign?on=2030-01-04\">← Dia anterior</a>\n <a class=\"button secondary \" href=\"/admin/assign?on=2026-10-07\">Hoje</a>\n <a class=\"button secondary\" href=\"/admin/assign?on=2030-01-06\">Dia seguinte →</a>\n</div>\n\n<div class=\"stack gap-lg top-space\">\n <section class=\"panel\">\n <h2>Sábado, 5 de janeiro de 2030</h2>\n \n <form method=\"post\" action=\"/admin/assign\" class=\"row between wrap gap top-space\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <input type=\"hidden\" name=\"assignment_id\" value=\"9\">\n <div>\n <strong>Noite</strong>\n <span class=\"muted\"> · 20:30–00:00</span>\n <div class=\"muted\">\n \n Agora: sem atribuição\n \n </div>\n </div>\n <div class=\"inline-form\">\n <select name=\"team_id\">\n <option value=\"auto\" selected>Automático → sem ninguém</option>\n <option value=\"\" >Sem atribuição</option>\n \n <option value=\"1\" >Administrador de teste</option>\n \n <option value=\"2\" >Equipa Norte</option>\n \n <option value=\"3\" >Equipa Sul</option>\n \n <option value=\"4\" >Equipa partilhada</option>\n \n </select>\n <button class=\"secondary small\" type=\"submit\">Atribuir</button>\n </div>\n </form>\n \n <form method=\"post\" action=\"/admin/assign\" class=\"row between wrap gap top-space\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <input type=\"hidden\" name=\"assignment_id\" value=\"36\">\n <div>\n <strong>Almoço - Sábados</strong>\n <span class=\"muted\"> · 13:00–16:00</span>\n <div class=\"muted\">\n \n Agora: sem atribuição\n \n </div>\n </div>\n <div class=\"inline-form\">\n <select name=\"team_id\">\n <option value=\"auto\" selected>Automático → sem ninguém</option>\n <option value=\"\" >Sem atribuição</option>\n \n <option value=\"1\" >Administrador de teste</option>\n \n <option value=\"2\" >Equipa Norte</option>\n \n <option value=\"3\" >Equipa Sul</option>\n \n <option value=\"4\" >Equipa partilhada</option>\n \n </select>\n <button class=\"secondary small\" type=\"submit\">Atribuir</button>\n </div>\n </form>\n \n <form method=\"post\" action=\"/admin/assign\" class=\"row between wrap gap top-space\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <input type=\"hidden\" name=\"assignment_id\" value=\"1\">\n <div>\n <strong>Rotação Phase 4</strong>\n <span class=\"muted\"> · 12:00–14:00</span>\n <div class=\"muted\">\n \n Agora: Equipa Norte (manual)\n \n </div>\n </div>\n <div class=\"inline-form\">\n <select name=\"team_id\">\n <option value=\"auto\" >Automático → Equipa Norte</option>\n <option value=\"\" >Sem atribuição</option>\n \n <option value=\"1\" >Administrador de teste</option>\n \n <option value=\"2\" selected>Equipa Norte</option>\n \n <option value=\"3\" >Equipa Sul</option>\n \n <option value=\"4\" >Equipa partilhada</option>\n \n </select>\n <button class=\"secondary small\" type=\"submit\">Atribuir</button>\n </div>\n </form>\n \n </section>\n <p class=\"muted\">Atribuir aqui fecha qualquer pedido de troca em aberto sobre esse turno e avisa a nova equipa e a anterior.</p>\n <p class=\"muted\">«Automático» repõe a equipa que a escala dá a este dia — a ordem da rotação ou o padrão de noites — e volta a marcar o turno como automático.</p>\n</div>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/admin/assign?on=2030-01-05",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Registo de e-mails · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/swaps\">Trocas</a>\n \n \n <a href=\"/admin/schedules\">Gerir escalas</a>\n <a href=\"/admin/teams\">Equipas</a>\n <a href=\"/admin/users\">Utilizadores</a>\n <a href=\"/admin/notifications\">Registo de e-mails</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/account\" title=\"A minha conta\">\n <span class=\"name-text\">Administrador de teste</span>\n <span class=\"muted\">@phase4-admin</span>\n </a>\n <form method=\"post\" action=\"/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n<div class=\"page-head\"><div><h1>Registo de e-mails</h1><p class=\"muted\">«ignorado» significa que a equipa não tem e-mail, que tem as notificações desligadas ou que o SMTP não está configurado.</p></div></div>\n<div class=\"table-wrap\"><table><thead><tr><th>Data e hora</th><th>Evento</th><th>Equipa</th><th>Destinatário</th><th>Estado</th><th>Assunto</th></tr></thead><tbody>\n<tr><td>07/10/2026 10:58</td><td>Troca revertida</td><td>Equipa Sul</td><td>b@example.com</td><td><span class=\"status status-skipped\">ignorado</span></td><td>Troca de turno revertida</td></tr><tr><td>07/10/2026 10:58</td><td>Troca revertida</td><td>Equipa Norte</td><td>a@example.com</td><td><span class=\"status status-skipped\">ignorado</span></td><td>Troca de turno revertida</td></tr><tr><td>07/10/2026 10:58</td><td>Troca aprovada</td><td>Equipa Sul</td><td>b@example.com</td><td><span class=\"status status-skipped\">ignorado</span></td><td>Troca de turno aprovada</td></tr><tr><td>07/10/2026 10:58</td><td>Troca aprovada</td><td>Equipa Norte</td><td>a@example.com</td><td><span class=\"status status-skipped\">ignorado</span></td><td>Troca de turno aprovada</td></tr><tr><td>07/10/2026 10:58</td><td>Troca aceite</td><td>Equipa Norte</td><td>a@example.com</td><td><span class=\"status status-skipped\">ignorado</span></td><td>A sua troca de turno foi aceite</td></tr><tr><td>07/10/2026 10:58</td><td>Troca aguarda aprovação</td><td>Administrador de teste</td><td>admin@example.com</td><td><span class=\"status status-skipped\">ignorado</span></td><td>Uma troca de turno precisa de aprovação</td></tr><tr><td>07/10/2026 10:58</td><td>Pedido de troca</td><td>Equipa Sul</td><td>b@example.com</td><td><span class=\"status status-skipped\">ignorado</span></td><td>Troca de turnos: sáb 05 jan por sáb 12 jan – Rotação Phase 4</td></tr>\n</tbody></table></div>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/admin/notifications",
"status": 200
},
{
"body": "",
"headers": {
"location": "/admin/schedules"
},
"method": "GET",
"path": "/admin/month",
"status": 303
},
{
"body": "",
"headers": {
"location": "/admin/schedules/4?year=2030&month=1"
},
"method": "POST",
"path": "/admin/schedules/4/apply-rotation",
"status": 303
},
{
"body": "",
"headers": {
"location": "/admin/schedules/4?year=2030&month=1"
},
"method": "POST",
"path": "/admin/schedules/4/clear-from",
"status": 303
},
{
"body": "",
"headers": {
"location": "/admin/assign?on=2030-01-26"
},
"method": "POST",
"path": "/admin/assign",
"status": 303
},
{
"body": "",
"headers": {
"location": "/admin/teams"
},
"method": "POST",
"path": "/admin/teams",
"status": 303
},
{
"body": "",
"headers": {
"location": "/admin/teams"
},
"method": "POST",
"path": "/admin/teams/5/update",
"status": 303
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Remover Equipa editada · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/swaps\">Trocas</a>\n \n \n <a href=\"/admin/schedules\">Gerir escalas</a>\n <a href=\"/admin/teams\">Equipas</a>\n <a href=\"/admin/users\">Utilizadores</a>\n <a href=\"/admin/notifications\">Registo de e-mails</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/account\" title=\"A minha conta\">\n <span class=\"name-text\">Administrador de teste</span>\n <span class=\"muted\">@phase4-admin</span>\n </a>\n <form method=\"post\" action=\"/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n <div class=\"flash info\" role=\"status\">Equipa atualizada.</div>\n \n \n<div class=\"page-head\">\n <div>\n <p class=\"eyebrow\"><a href=\"/admin/teams\">Equipas</a></p>\n <h1>Remover equipa</h1>\n <p class=\"muted\">Isto é definitivo e não pode ser desfeito.</p>\n </div>\n</div>\n<section class=\"panel\">\n <h2>Equipa editada</h2>\n <p>Sem utilizador, apenas aparece na escala.</p>\n <p class=\"muted\">Só é possível remover quem ainda não tem turnos atribuídos. Primeiro transfira os turnos desta equipa para outras; assim o registo de quem trabalhou cada data mantém-se.</p>\n <p class=\"muted\">Ao remover, acontece o seguinte:</p>\n <ul class=\"link-list\">\n <li>0 posição(ões) saem das rotações.</li>\n \n \n <li>O histórico de e-mails é mantido, sem ligação a esta equipa.</li>\n </ul>\n <p class=\"muted\">Se só quiser que esta equipa deixe de receber turnos, desative-a em vez de a remover.</p>\n <div class=\"actions\">\n <form method=\"post\" action=\"/admin/teams/5/delete\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"danger\" type=\"submit\">Remover definitivamente</button>\n </form>\n <a class=\"button secondary\" href=\"/admin/teams\">Cancelar</a>\n </div>\n</section>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/admin/teams/5/delete",
"status": 200
},
{
"body": "",
"headers": {
"location": "/admin/teams"
},
"method": "POST",
"path": "/admin/teams/5/delete",
"status": 303
},
{
"body": "",
"headers": {
"location": "/admin/users"
},
"method": "POST",
"path": "/admin/users",
"status": 303
},
{
"body": "",
"headers": {
"location": "/admin/users"
},
"method": "POST",
"path": "/admin/users/4/update",
"status": 303
},
{
"body": "",
"headers": {
"location": "/admin/users"
},
"method": "POST",
"path": "/admin/users/4/delete",
"status": 303
},
{
"body": "",
"headers": {
"location": "/"
},
"method": "POST",
"path": "/account",
"status": 303
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>A minha conta · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/swaps\">Trocas</a>\n <a href=\"/export/schedule\">Exportar PDF</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/account\" title=\"A minha conta\">\n <span class=\"name-text\">Membro a</span>\n <span class=\"muted\">@member-a</span>\n </a>\n <form method=\"post\" action=\"/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n <div class=\"flash error\" role=\"status\">A nova palavra-passe é igual à atual.</div>\n \n \n<div class=\"page-head\">\n <div>\n <p class=\"eyebrow\"><a href=\"/\">Rota</a></p>\n <h1>A minha conta</h1>\n <p class=\"muted\">Membro a · @member-a</p>\n \n <p class=\"muted\">Este utilizador entra em nome de 2 equipa(s): Equipa Norte, Equipa partilhada. Vê e controla os turnos de todas.</p>\n \n </div>\n</div>\n\n<section class=\"panel\">\n <h2>Alterar palavra-passe</h2>\n <form method=\"post\" action=\"/account\" class=\"form-grid\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <input type=\"password\" name=\"new_password\" minlength=\"4\" autocomplete=\"new-password\" required autofocus>\n <div><button class=\"primary\" type=\"submit\">Alterar palavra-passe</button></div>\n </form>\n</section>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/account",
"status": 200
},
{
"body": "",
"headers": {
"location": "/"
},
"method": "POST",
"path": "/account",
"status": 303
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>A minha conta · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/swaps\">Trocas</a>\n <a href=\"/export/schedule\">Exportar PDF</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/account\" title=\"A minha conta\">\n <span class=\"name-text\">Membro a</span>\n <span class=\"muted\">@member-a</span>\n </a>\n <form method=\"post\" action=\"/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n <div class=\"flash info\" role=\"status\">Palavra-passe alterada.</div>\n \n \n<div class=\"page-head\">\n <div>\n <p class=\"eyebrow\"><a href=\"/\">Rota</a></p>\n <h1>A minha conta</h1>\n <p class=\"muted\">Membro a · @member-a</p>\n \n <p class=\"muted\">Este utilizador entra em nome de 2 equipa(s): Equipa Norte, Equipa partilhada. Vê e controla os turnos de todas.</p>\n \n </div>\n</div>\n\n<section class=\"panel\">\n <h2>Alterar palavra-passe</h2>\n <form method=\"post\" action=\"/account\" class=\"form-grid\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <input type=\"password\" name=\"new_password\" minlength=\"4\" autocomplete=\"new-password\" required autofocus>\n <div><button class=\"primary\" type=\"submit\">Alterar palavra-passe</button></div>\n </form>\n</section>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/account",
"status": 200
},
{
"body": "",
"headers": {
"location": "/login"
},
"method": "POST",
"path": "/logout",
"status": 303
},
{
"body": "{\"detail\":\"É necessário iniciar sessão\"}",
"headers": {
"content-type": "application/json"
},
"method": "GET",
"path": "/account",
"status": 401
}
],
"rows": {
"alembic_version": [
{
"version_num": "0001_initial"
}
],
"assignments": [
{
"date": "2030-01-05",
"id": 1,
"note": "Revertida a troca #1 por Administrador de teste",
"schedule_id": 4,
"source": "manual",
"team_id": 2,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-12",
"id": 2,
"note": "Revertida a troca #1 por Administrador de teste",
"schedule_id": 4,
"source": "manual",
"team_id": 3,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-19",
"id": 3,
"note": null,
"schedule_id": 4,
"source": "swap",
"team_id": 2,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-26",
"id": 4,
"note": null,
"schedule_id": 4,
"source": "manual",
"team_id": 3,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-01",
"id": 5,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-02",
"id": 6,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-03",
"id": 7,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-04",
"id": 8,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-05",
"id": 9,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-06",
"id": 10,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-07",
"id": 11,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-08",
"id": 12,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-09",
"id": 13,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-10",
"id": 14,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-11",
"id": 15,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-12",
"id": 16,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-13",
"id": 17,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-14",
"id": 18,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-15",
"id": 19,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-16",
"id": 20,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-17",
"id": 21,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-18",
"id": 22,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-19",
"id": 23,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-20",
"id": 24,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-21",
"id": 25,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-22",
"id": 26,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-23",
"id": 27,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-24",
"id": 28,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-25",
"id": 29,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-26",
"id": 30,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-27",
"id": 31,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-28",
"id": 32,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-29",
"id": 33,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-30",
"id": 34,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-31",
"id": 35,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-05",
"id": 36,
"note": null,
"schedule_id": 2,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-12",
"id": 37,
"note": null,
"schedule_id": 2,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-19",
"id": 38,
"note": null,
"schedule_id": 2,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-26",
"id": 39,
"note": null,
"schedule_id": 2,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-06",
"id": 40,
"note": null,
"schedule_id": 3,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-13",
"id": 41,
"note": null,
"schedule_id": 3,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-20",
"id": 42,
"note": null,
"schedule_id": 3,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-27",
"id": 43,
"note": null,
"schedule_id": 3,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
}
],
"monthly_patterns": [],
"notification_logs": [
{
"body": "Equipa Norte propõe trocar o turno de Rotação Phase 4 de 5 de janeiro de 2030 pelo seu turno de 12 de janeiro de 2030.\n\nTeste de troca\n\nAbrir a escala: http://testserver/swaps",
"channel": "email",
"created_at": "<timestamp>",
"error": "SMTP_HOST não está configurado",
"event_type": "swap_requested",
"id": 1,
"recipient": "b@example.com",
"sent_at": null,
"status": "skipped",
"subject": "Troca de turnos: sáb 05 jan por sáb 12 jan – Rotação Phase 4",
"team_id": 3
},
{
"body": "Equipa Sul aceitou a troca de Rotação Phase 4 de 5 de janeiro de 2030 por 12 de janeiro de 2030, pedida por Equipa Norte. Aprove em http://testserver/swaps",
"channel": "email",
"created_at": "<timestamp>",
"error": "SMTP_HOST não está configurado",
"event_type": "swap_needs_approval",
"id": 2,
"recipient": "admin@example.com",
"sent_at": null,
"status": "skipped",
"subject": "Uma troca de turno precisa de aprovação",
"team_id": 1
},
{
"body": "Equipa Sul aceitou o seu pedido. Está agora à espera da aprovação da gestão.",
"channel": "email",
"created_at": "<timestamp>",
"error": "SMTP_HOST não está configurado",
"event_type": "swap_accepted",
"id": 3,
"recipient": "a@example.com",
"sent_at": null,
"status": "skipped",
"subject": "A sua troca de turno foi aceite",
"team_id": 2
},
{
"body": "Administrador de teste aprovou a troca de Rotação Phase 4: Equipa Sul fica com o turno de 5 de janeiro de 2030 e Equipa Norte com o de 12 de janeiro de 2030.",
"channel": "email",
"created_at": "<timestamp>",
"error": "SMTP_HOST não está configurado",
"event_type": "swap_approved",
"id": 4,
"recipient": "a@example.com",
"sent_at": null,
"status": "skipped",
"subject": "Troca de turno aprovada",
"team_id": 2
},
{
"body": "Administrador de teste aprovou a troca de Rotação Phase 4: Equipa Sul fica com o turno de 5 de janeiro de 2030 e Equipa Norte com o de 12 de janeiro de 2030.",
"channel": "email",
"created_at": "<timestamp>",
"error": "SMTP_HOST não está configurado",
"event_type": "swap_approved",
"id": 5,
"recipient": "b@example.com",
"sent_at": null,
"status": "skipped",
"subject": "Troca de turno aprovada",
"team_id": 3
},
{
"body": "Administrador de teste reverteu a troca de Rotação Phase 4. Equipa Norte volta a 5 de janeiro de 2030 e Equipa Sul volta a 12 de janeiro de 2030.\n\nVer a escala: http://testserver/",
"channel": "email",
"created_at": "<timestamp>",
"error": "SMTP_HOST não está configurado",
"event_type": "swap_reverted",
"id": 6,
"recipient": "a@example.com",
"sent_at": null,
"status": "skipped",
"subject": "Troca de turno revertida",
"team_id": 2
},
{
"body": "Administrador de teste reverteu a troca de Rotação Phase 4. Equipa Norte volta a 5 de janeiro de 2030 e Equipa Sul volta a 12 de janeiro de 2030.\n\nVer a escala: http://testserver/",
"channel": "email",
"created_at": "<timestamp>",
"error": "SMTP_HOST não está configurado",
"event_type": "swap_reverted",
"id": 7,
"recipient": "b@example.com",
"sent_at": null,
"status": "skipped",
"subject": "Troca de turno revertida",
"team_id": 3
},
{
"body": "Já não está atribuído ao turno de Rotação Phase 4 a 26 de janeiro de 2030.\n\nVer a escala: http://testserver/",
"channel": "email",
"created_at": "<timestamp>",
"error": "SMTP_HOST não está configurado",
"event_type": "assignment_changed",
"id": 8,
"recipient": "b@example.com",
"sent_at": null,
"status": "skipped",
"subject": "Escala atualizada: sáb 26 jan – Rotação Phase 4",
"team_id": 3
},
{
"body": "Está atribuído ao turno de Rotação Phase 4 a 26 de janeiro de 2030.\n\nVer a escala: http://testserver/",
"channel": "email",
"created_at": "<timestamp>",
"error": "SMTP_HOST não está configurado",
"event_type": "assignment_updated",
"id": 9,
"recipient": "b@example.com",
"sent_at": null,
"status": "skipped",
"subject": "Escala atualizada: sáb 26 jan – Rotação Phase 4",
"team_id": 3
}
],
"rotation_members": [
{
"id": 1,
"position": 1,
"schedule_id": 4,
"team_id": 2
},
{
"id": 2,
"position": 2,
"schedule_id": 4,
"team_id": 3
}
],
"schedules": [
{
"created_at": "<timestamp>",
"end_time": "00:00:00.000000",
"id": 1,
"is_active": 1,
"name": "Noite",
"requires_manager_approval": 1,
"rotation_anchor_date": null,
"schedule_type": "fixed",
"slug": "night",
"start_time": "20:30:00.000000",
"weekdays": "0,1,2,3,4,5,6"
},
{
"created_at": "<timestamp>",
"end_time": "16:00:00.000000",
"id": 2,
"is_active": 1,
"name": "Almoço - Sábados",
"requires_manager_approval": 1,
"rotation_anchor_date": "2024-01-06",
"schedule_type": "rotation",
"slug": "saturday-lunch",
"start_time": "13:00:00.000000",
"weekdays": "5"
},
{
"created_at": "<timestamp>",
"end_time": "16:00:00.000000",
"id": 3,
"is_active": 1,
"name": "Almoço - Domingo",
"requires_manager_approval": 1,
"rotation_anchor_date": "2024-01-07",
"schedule_type": "rotation",
"slug": "sunday-lunch",
"start_time": "13:00:00.000000",
"weekdays": "6"
},
{
"created_at": "<timestamp>",
"end_time": "14:00:00.000000",
"id": 4,
"is_active": 1,
"name": "Rotação Phase 4",
"requires_manager_approval": 1,
"rotation_anchor_date": "2030-01-05",
"schedule_type": "rotation",
"slug": "phase4-rotation",
"start_time": "12:00:00.000000",
"weekdays": "5"
}
],
"swap_requests": [
{
"accepted_team_id": 3,
"assignment_id": 1,
"created_at": "<timestamp>",
"id": 1,
"message": "Teste de troca",
"requester_id": 2,
"status": "reverted",
"target_assignment_id": 2,
"target_team_id": 3,
"updated_at": "<timestamp>"
}
],
"teams": [
{
"calendar_token": null,
"created_at": "<timestamp>",
"email": "admin@example.com",
"id": 1,
"is_active": 1,
"name": "Administrador de teste",
"notify_email": 1,
"phone": null,
"user_id": 1
},
{
"calendar_token": "phase4-feed-a",
"created_at": "<timestamp>",
"email": "a@example.com",
"id": 2,
"is_active": 1,
"name": "Equipa Norte",
"notify_email": 1,
"phone": null,
"user_id": 2
},
{
"calendar_token": "phase4-feed-b",
"created_at": "<timestamp>",
"email": "b@example.com",
"id": 3,
"is_active": 1,
"name": "Equipa Sul",
"notify_email": 1,
"phone": null,
"user_id": 3
},
{
"calendar_token": "phase4-feed-c",
"created_at": "<timestamp>",
"email": "c@example.com",
"id": 4,
"is_active": 1,
"name": "Equipa partilhada",
"notify_email": 1,
"phone": null,
"user_id": 2
}
],
"users": [
{
"created_at": "<timestamp>",
"id": 1,
"is_active": 1,
"is_admin": 1,
"name": "Administrador de teste",
"password_hash": "<password-hash>",
"username": "phase4-admin"
},
{
"created_at": "<timestamp>",
"id": 2,
"is_active": 1,
"is_admin": 0,
"name": "Membro a",
"password_hash": "<password-hash>",
"username": "member-a"
},
{
"created_at": "<timestamp>",
"id": 3,
"is_active": 1,
"is_admin": 0,
"name": "Membro b",
"password_hash": "<password-hash>",
"username": "member-b"
}
]
}
},
"/crr": {
"requests": [
{
"body": "{\"status\":\"ok\"}",
"headers": {
"content-type": "application/json"
},
"method": "GET",
"path": "/health",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Entrar · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/crr/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/crr/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/crr/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n </nav>\n \n <div class=\"account\"><a href=\"/crr/login\">Entrar</a></div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n<section class=\"auth-card\">\n <div class=\"auth-brand\" aria-hidden=\"true\">\n <img src=\"http://testserver/crr/brand/assets/logo.png\" alt=\"\">\n </div>\n <h1>Entrar</h1>\n <p class=\"muted\">Use o seu utilizador e a sua palavra-passe da escala.</p>\n <form method=\"post\" action=\"/crr/login\" class=\"stack\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <label>Utilizador\n <input name=\"username\" autocomplete=\"username\" required autofocus>\n </label>\n <label>Palavra-passe\n <input type=\"password\" name=\"password\" autocomplete=\"current-password\" required>\n </label>\n <button class=\"primary\" type=\"submit\">Entrar</button>\n </form>\n</section>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/login",
"status": 200
},
{
"body": "{\"detail\":\"É necessário iniciar sessão\"}",
"headers": {
"content-type": "application/json"
},
"method": "GET",
"path": "/account",
"status": 401
},
{
"body": "{\"detail\":\"É necessário iniciar sessão\"}",
"headers": {
"content-type": "application/json"
},
"method": "GET",
"path": "/admin/teams",
"status": 401
},
{
"body": "{\"detail\":\"É necessário iniciar sessão\"}",
"headers": {
"content-type": "application/json"
},
"method": "GET",
"path": "/admin/users",
"status": 401
},
{
"body": "{\"detail\":\"É necessário iniciar sessão\"}",
"headers": {
"content-type": "application/json"
},
"method": "GET",
"path": "/admin/schedules",
"status": 401
},
{
"body": "{\"detail\":\"É necessário iniciar sessão\"}",
"headers": {
"content-type": "application/json"
},
"method": "GET",
"path": "/swaps",
"status": 401
},
{
"body": "{\"detail\":\"Token CSRF inválido\"}",
"headers": {
"content-type": "application/json"
},
"method": "POST",
"path": "/login",
"status": 400
},
{
"body": "",
"headers": {
"location": "/crr/"
},
"method": "POST",
"path": "/login",
"status": 303
},
{
"body": "",
"headers": {
"location": "/crr/"
},
"method": "POST",
"path": "/login",
"status": 303
},
{
"body": "",
"headers": {
"location": "/crr/"
},
"method": "POST",
"path": "/login",
"status": 303
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>A minha conta · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/crr/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/crr/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/crr/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/crr/swaps\">Trocas</a>\n <a href=\"/crr/export/schedule\">Exportar PDF</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/crr/account\" title=\"A minha conta\">\n <span class=\"name-text\">Membro a</span>\n <span class=\"muted\">@member-a</span>\n </a>\n <form method=\"post\" action=\"/crr/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n <div class=\"flash info\" role=\"status\">Bem-vindo(a), Equipa Norte, Equipa partilhada.</div>\n \n \n<div class=\"page-head\">\n <div>\n <p class=\"eyebrow\"><a href=\"/crr/\">Rota</a></p>\n <h1>A minha conta</h1>\n <p class=\"muted\">Membro a · @member-a</p>\n \n <p class=\"muted\">Este utilizador entra em nome de 2 equipa(s): Equipa Norte, Equipa partilhada. Vê e controla os turnos de todas.</p>\n \n </div>\n</div>\n\n<section class=\"panel\">\n <h2>Alterar palavra-passe</h2>\n <form method=\"post\" action=\"/crr/account\" class=\"form-grid\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <input type=\"password\" name=\"new_password\" minlength=\"4\" autocomplete=\"new-password\" required autofocus>\n <div><button class=\"primary\" type=\"submit\">Alterar palavra-passe</button></div>\n </form>\n</section>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/account",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Calendário · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/crr/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/crr/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/crr/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/crr/swaps\">Trocas</a>\n <a href=\"/crr/export/schedule\">Exportar PDF</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/crr/account\" title=\"A minha conta\">\n <span class=\"name-text\">Membro a</span>\n <span class=\"muted\">@member-a</span>\n </a>\n <form method=\"post\" action=\"/crr/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n<div class=\"page-head\">\n <div>\n <p class=\"eyebrow\"><a href=\"/crr/\">Rota</a></p>\n <h1>Calendário</h1>\n <p class=\"muted\">Os teus turnos na app de calendário do telemóvel. Subscreve uma vez: as alterações à rota aparecem automaticamente, quando a app atualizar o calendário.</p>\n </div>\n</div>\n\n<div class=\"stack gap-lg\">\n\n <section class=\"panel calendar-feed\">\n <h2>Turnos de Equipa Norte</h2>\n <div class=\"calendar-subscribe-actions\" aria-label=\"Adicionar Turnos de Equipa Norte\">\n <a class=\"button primary\" href=\"webcal://testserver/crr/calendar/phase4-feed-a.ics\">iPhone / Apple Calendar</a>\n <a class=\"button secondary\" href=\"https://calendar.google.com/calendar/render?cid=http%3A%2F%2Ftestserver%2Fcrr%2Fcalendar%2Fphase4-feed-a.ics\" target=\"_blank\" rel=\"noopener noreferrer\">Google Calendar / Android</a>\n <a class=\"button secondary\" href=\"https://outlook.live.com/calendar/0/addfromweb?url=http%3A%2F%2Ftestserver%2Fcrr%2Fcalendar%2Fphase4-feed-a.ics&amp;name=Turnos+de+Equipa+Norte\" target=\"_blank\" rel=\"noopener noreferrer\">Outlook</a>\n </div>\n <p class=\"muted\">No iPhone, confirma a subscrição na app Calendário. No Google e Outlook, adiciona pelo navegador à conta que usas no telemóvel. <a href=\"#calendar-help\">Ver os passos</a>.</p>\n <label for=\"calendar-url-1\">Endereço para subscrever noutra app</label>\n <div class=\"calendar-url-row\">\n <input id=\"calendar-url-1\" type=\"text\" readonly value=\"http://testserver/crr/calendar/phase4-feed-a.ics\" inputmode=\"url\" autocapitalize=\"off\" spellcheck=\"false\">\n <button class=\"secondary\" type=\"button\" data-copy=\"http://testserver/crr/calendar/phase4-feed-a.ics\">Copiar endereço</button>\n </div>\n <p class=\"calendar-copy-status muted\" role=\"status\" data-copy-status></p>\n </section>\n\n <section class=\"panel calendar-feed\">\n <h2>Turnos de Equipa partilhada</h2>\n <div class=\"calendar-subscribe-actions\" aria-label=\"Adicionar Turnos de Equipa partilhada\">\n <a class=\"button primary\" href=\"webcal://testserver/crr/calendar/phase4-feed-c.ics\">iPhone / Apple Calendar</a>\n <a class=\"button secondary\" href=\"https://calendar.google.com/calendar/render?cid=http%3A%2F%2Ftestserver%2Fcrr%2Fcalendar%2Fphase4-feed-c.ics\" target=\"_blank\" rel=\"noopener noreferrer\">Google Calendar / Android</a>\n <a class=\"button secondary\" href=\"https://outlook.live.com/calendar/0/addfromweb?url=http%3A%2F%2Ftestserver%2Fcrr%2Fcalendar%2Fphase4-feed-c.ics&amp;name=Turnos+de+Equipa+partilhada\" target=\"_blank\" rel=\"noopener noreferrer\">Outlook</a>\n </div>\n <p class=\"muted\">No iPhone, confirma a subscrição na app Calendário. No Google e Outlook, adiciona pelo navegador à conta que usas no telemóvel. <a href=\"#calendar-help\">Ver os passos</a>.</p>\n <label for=\"calendar-url-2\">Endereço para subscrever noutra app</label>\n <div class=\"calendar-url-row\">\n <input id=\"calendar-url-2\" type=\"text\" readonly value=\"http://testserver/crr/calendar/phase4-feed-c.ics\" inputmode=\"url\" autocapitalize=\"off\" spellcheck=\"false\">\n <button class=\"secondary\" type=\"button\" data-copy=\"http://testserver/crr/calendar/phase4-feed-c.ics\">Copiar endereço</button>\n </div>\n <p class=\"calendar-copy-status muted\" role=\"status\" data-copy-status></p>\n </section>\n\n</div>\n\n<section class=\"panel calendar-help top-space\" id=\"calendar-help\">\n <h2>Como adicionar no telemóvel</h2>\n <details>\n <summary>iPhone / iPad — Apple Calendar</summary>\n <ol>\n <li>Toca em <strong>iPhone / Apple Calendar</strong> no calendário que queres adicionar.</li>\n <li>Permite abrir a app Calendário e confirma <strong>Subscrever</strong>.</li>\n <li>Se o botão não abrir a app, copia o endereço. Na app Calendário, abre <strong>Calendários → Adicionar calendário → Adicionar calendário de assinatura</strong> e cola-o.</li>\n </ol>\n </details>\n <details>\n <summary>Android / Google Calendar</summary>\n <ol>\n <li>Toca em <strong>Google Calendar / Android</strong> e entra na conta Google que usas no telemóvel.</li>\n <li>O Google exige a versão para computador para adicionar subscrições. No navegador do telemóvel, ativa <strong>Site para computador</strong>; se não conseguires, abre esta página num computador.</li>\n <li>Confirma a adição. Se não aparecer essa opção, copia o endereço e, no Google Calendar, escolhe <strong>Outros calendários → + → A partir de URL</strong>, cola-o e adiciona o calendário.</li>\n <li>Na app Google Calendar, abre o menu e ativa o novo calendário. Se não aparecer, verifica as definições de sincronização.</li>\n </ol>\n </details>\n <details>\n <summary>Outlook</summary>\n <ol>\n <li>Toca em <strong>Outlook</strong> e entra na conta que usas na app.</li>\n <li>Confirma a subscrição no Outlook na Web. Se necessário, usa <strong>Adicionar calendário → Subscrever a partir da Web</strong> e cola o endereço copiado.</li>\n <li>Na app Outlook, seleciona o novo calendário nessa conta. Se a versão móvel não mostrar a opção de subscrever, usa o navegador em modo computador.</li>\n </ol>\n </details>\n <p class=\"muted\">Escolhe uma subscrição por endereço, em vez de importar um ficheiro .ics, para receber futuras alterações. A atualização depende da app e pode demorar algumas horas. Para Google e Outlook, o endereço tem de estar acessível pela Internet.</p>\n</section>\n\n<script>\n // Clipboard permissions can fail even on HTTPS; keep a mobile-friendly fallback.\n document.querySelectorAll(\"[data-copy]\").forEach(function (button) {\n button.addEventListener(\"click\", async function () {\n var url = button.dataset.copy;\n var field = button.parentElement.querySelector(\"input\");\n var status = button.closest(\".calendar-feed\").querySelector(\"[data-copy-status]\");\n var copied = false;\n if (navigator.clipboard && window.isSecureContext) {\n try {\n await navigator.clipboard.writeText(url);\n copied = true;\n } catch (error) {\n // Fall through to selection/copy when clipboard access is denied.\n }\n }\n if (!copied) {\n field.focus();\n field.select();\n field.setSelectionRange(0, field.value.length);\n try {\n copied = document.execCommand(\"copy\");\n } catch (error) {\n // The selected field still supports the phone's native Copy action.\n }\n }\n if (copied) {\n button.textContent = \"Copiado\";\n status.textContent = \"Endereço copiado. Cola-o na tua app de calendário.\";\n setTimeout(function () { button.textContent = \"Copiar endereço\"; }, 2000);\n } else {\n status.textContent = \"Endereço selecionado. Toque no campo e escolha «Copiar».\";\n }\n });\n });\n</script>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/calendar",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/crr/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/crr/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/crr/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/crr/swaps\">Trocas</a>\n <a href=\"/crr/export/schedule\">Exportar PDF</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/crr/account\" title=\"A minha conta\">\n <span class=\"name-text\">Membro a</span>\n <span class=\"muted\">@member-a</span>\n </a>\n <form method=\"post\" action=\"/crr/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n<div class=\"page-head\">\n <div><h1>Pedidos de troca</h1></div>\n</div>\n\n<div class=\"stack gap-lg\">\n\n <div class=\"empty\">Ainda não há pedidos de troca.</div>\n\n</div>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/swaps",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Pedir troca · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/crr/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/crr/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/crr/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/crr/swaps\">Trocas</a>\n <a href=\"/crr/export/schedule\">Exportar PDF</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/crr/account\" title=\"A minha conta\">\n <span class=\"name-text\">Membro a</span>\n <span class=\"muted\">@member-a</span>\n </a>\n <form method=\"post\" action=\"/crr/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n<div class=\"page-head\">\n <div>\n <p class=\"eyebrow\"><a href=\"/crr/swaps\">Trocas</a></p>\n <h1>Pedir troca</h1>\n <p class=\"muted\">Uma troca é entre duas equipas da mesma escala: cada uma fica com o turno da outra.</p>\n </div>\n <div class=\"month-nav\">\n <a class=\"button secondary\" href=\"/crr/swaps/new?year=2029&amp;month=12\">← Anterior</a>\n <a class=\"button secondary\" href=\"/crr/swaps/new?year=2030&amp;month=2\">Seguinte →</a>\n </div>\n</div>\n\n<div class=\"stack gap-lg\">\n\n <section class=\"panel\">\n <div class=\"row between wrap gap\">\n <h2>Rotação Phase 4</h2>\n <span class=\"muted\">Janeiro 2030</span>\n </div>\n <form method=\"post\" action=\"/crr/swaps/new\" class=\"form-grid\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <label>O turno que passo\n <select name=\"assignment_id\" required>\n <option value=\"1\">sáb 05 jan · Equipa Norte</option><option value=\"3\">sáb 19 jan · Equipa Norte</option>\n </select>\n </label>\n <label>O turno que quero em troca\n <select name=\"target_assignment_id\" required>\n <option value=\"2\">sáb 12 jan · Equipa Sul</option><option value=\"4\">sáb 26 jan · Equipa Sul</option>\n </select>\n </label>\n <label>Mensagem <span class=\"muted\">(opcional)</span>\n <input name=\"message\" maxlength=\"500\" placeholder=\"Não consigo fazer este dia…\">\n </label>\n <div class=\"row end\"><button class=\"primary\" type=\"submit\">Enviar pedido</button></div>\n </form>\n </section>\n\n</div>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/swaps/new?year=2030&month=1",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/crr/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/crr/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/crr/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/crr/swaps\">Trocas</a>\n <a href=\"/crr/export/schedule\">Exportar PDF</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/crr/account\" title=\"A minha conta\">\n <span class=\"name-text\">Membro a</span>\n <span class=\"muted\">@member-a</span>\n </a>\n <form method=\"post\" action=\"/crr/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n<div class=\"page-head\">\n <div>\n <h1>Janeiro 2030</h1>\n </div>\n <div class=\"month-nav\">\n \n <a class=\"button secondary\" href=\"/crr/calendar\">Calendário</a>\n <a class=\"button primary\" href=\"/crr/swaps/new?year=2030&amp;month=1\">Pedir troca</a>\n \n <a class=\"button secondary\" href=\"/crr/?year=2029&amp;month=12\">← Anterior</a>\n <a class=\"button secondary\" href=\"/crr/?year=2030&amp;month=2\">Seguinte →</a>\n </div>\n</div>\n\n<div class=\"calendar-list\">\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Ter</strong>\n <span>1</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Qua</strong>\n <span>2</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Qui</strong>\n <span>3</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Sex</strong>\n <span>4</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Sáb</strong>\n <span>5</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Almoço - Sábados</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment mine\">\n <div>\n <div class=\"assignment-title\">Rotação Phase 4</div>\n <div class=\"assignment-team\">\n Equipa Norte\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Dom</strong>\n <span>6</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Almoço - Domingo</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Seg</strong>\n <span>7</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Ter</strong>\n <span>8</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Qua</strong>\n <span>9</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Qui</strong>\n <span>10</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Sex</strong>\n <span>11</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Sáb</strong>\n <span>12</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Almoço - Sábados</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Rotação Phase 4</div>\n <div class=\"assignment-team\">\n Equipa Sul\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Dom</strong>\n <span>13</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Almoço - Domingo</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Seg</strong>\n <span>14</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Ter</strong>\n <span>15</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Qua</strong>\n <span>16</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Qui</strong>\n <span>17</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Sex</strong>\n <span>18</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Sáb</strong>\n <span>19</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Almoço - Sábados</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment mine\">\n <div>\n <div class=\"assignment-title\">Rotação Phase 4</div>\n <div class=\"assignment-team\">\n Equipa Norte\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Dom</strong>\n <span>20</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Almoço - Domingo</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Seg</strong>\n <span>21</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Ter</strong>\n <span>22</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Qua</strong>\n <span>23</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Qui</strong>\n <span>24</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Sex</strong>\n <span>25</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Sáb</strong>\n <span>26</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Almoço - Sábados</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Rotação Phase 4</div>\n <div class=\"assignment-team\">\n Equipa Sul\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Dom</strong>\n <span>27</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Almoço - Domingo</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Seg</strong>\n <span>28</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Ter</strong>\n <span>29</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Qua</strong>\n <span>30</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n <section class=\"day-card \">\n <div class=\"day-label\">\n <strong>Qui</strong>\n <span>31</span>\n </div>\n <div class=\"day-assignments\">\n \n <article class=\"assignment \">\n <div>\n <div class=\"assignment-title\">Noite</div>\n <div class=\"assignment-team\">\n <span class=\"unassigned\">Sem atribuição</span>\n </div>\n </div>\n \n \n </article>\n \n </div>\n </section>\n\n</div>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/?year=2030&month=1",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Exportar escala · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/crr/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/crr/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/crr/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/crr/swaps\">Trocas</a>\n <a href=\"/crr/export/schedule\">Exportar PDF</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/crr/account\" title=\"A minha conta\">\n <span class=\"name-text\">Membro a</span>\n <span class=\"muted\">@member-a</span>\n </a>\n <form method=\"post\" action=\"/crr/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n<div class=\"page-head\">\n <div>\n <p class=\"eyebrow\"><a href=\"/crr/\">Rota</a></p>\n <h1>Exportar escala</h1>\n <p class=\"muted\">Escolhe uma escala e as datas. O PDF é descarregado numa única folha A4, com os dias e as equipas agrupados por mês.</p>\n </div>\n</div>\n<section class=\"panel\">\n \n<form class=\"stack\" method=\"get\" action=\"/crr/export/schedule.pdf\">\n <label>Escala\n <select name=\"schedule_id\" required>\n \n <option value=\"1\" selected>Noite</option>\n \n <option value=\"2\" >Almoço - Sábados</option>\n \n <option value=\"3\" >Almoço - Domingo</option>\n \n <option value=\"4\" >Rotação Phase 4</option>\n \n </select>\n </label>\n <div class=\"form-grid\">\n <label>Data inicial\n <input type=\"date\" name=\"start_date\" value=\"2026-10-01\" min=\"2000-01-01\" max=\"2100-12-31\" required>\n </label>\n <label>Data final\n <input type=\"date\" name=\"end_date\" value=\"2026-10-31\" min=\"2000-01-01\" max=\"2100-12-31\" required>\n </label>\n </div>\n <p class=\"muted\">As datas inicial e final estão incluídas. Máximo de 12 meses; o tamanho do texto adapta-se ao intervalo.</p>\n <div class=\"row end\"><button class=\"primary\" type=\"submit\">Descarregar PDF</button></div>\n</form>\n</section>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/export/schedule",
"status": 200
},
{
"body": "{\"detail\":\"É necessária a permissão de administrador\"}",
"headers": {
"content-type": "application/json"
},
"method": "GET",
"path": "/admin/users",
"status": 403
},
{
"body": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Contínuos CRR//Rota//PT\r\nCALSCALE:GREGORIAN\r\nMETHOD:PUBLISH\r\nX-WR-CALNAME:Contínuos CRR — Equipa Norte\r\nX-WR-CALDESC:Contínuos CRR — 1 de agosto de 2026 a 31 de março de 2027\r\nREFRESH-INTERVAL;VALUE=DURATION:PT12H\r\nX-PUBLISHED-TTL:PT12H\r\nX-WR-TIMEZONE:Europe/Lisbon\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Lisbon\r\nX-LIC-LOCATION:Europe/Lisbon\r\nBEGIN:DAYLIGHT\r\nTZOFFSETFROM:+0000\r\nTZOFFSETTO:+0100\r\nTZNAME:WEST\r\nDTSTART:19700329T020000\r\nRRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=-1SU\r\nEND:DAYLIGHT\r\nBEGIN:STANDARD\r\nTZOFFSETFROM:+0100\r\nTZOFFSETTO:+0000\r\nTZNAME:WET\r\nDTSTART:19701025T030000\r\nRRULE:FREQ=YEARLY;BYMONTH=10;BYDAY=-1SU\r\nEND:STANDARD\r\nEND:VTIMEZONE\r\nEND:VCALENDAR\r\n",
"headers": {
"cache-control": "private, max-age=300",
"content-disposition": "inline; filename=\"rota.ics\"",
"content-type": "text/calendar; charset=utf-8"
},
"method": "GET",
"path": "/calendar/phase4-feed-a.ics",
"status": 200
},
{
"body": "{\"detail\":\"Calendário não encontrado\"}",
"headers": {
"content-type": "application/json"
},
"method": "GET",
"path": "/calendar/not-a-token.ics",
"status": 404
},
{
"body": "CONTÍNUOS CRR\nRotação Phase 4\nJaneiro 2030\n 5\nEquipa Norte\n12\nEquipa Sul\n19\nEquipa Norte\n26\nEquipa Sul\n",
"headers": {
"cache-control": "private, no-store",
"content-disposition": "attachment; filename=\"escala-4-2030-01-01-2030-01-31.pdf\"",
"content-type": "application/pdf"
},
"method": "GET",
"path": "/export/schedule.pdf?schedule_id=4&start_date=2030-01-01&end_date=2030-01-31",
"status": 200
},
{
"body": "",
"headers": {
"location": "/crr/swaps"
},
"method": "POST",
"path": "/assignments/1/swap",
"status": 303
},
{
"body": "",
"headers": {
"location": "/crr/swaps"
},
"method": "POST",
"path": "/swaps/1/accept",
"status": 303
},
{
"body": "",
"headers": {
"location": "/crr/swaps"
},
"method": "POST",
"path": "/swaps/1/approve",
"status": 303
},
{
"body": "",
"headers": {
"location": "/crr/swaps"
},
"method": "POST",
"path": "/swaps/1/revert",
"status": 303
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Equipas · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/crr/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/crr/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/crr/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/crr/swaps\">Trocas</a>\n \n \n <a href=\"/crr/admin/schedules\">Gerir escalas</a>\n <a href=\"/crr/admin/teams\">Equipas</a>\n <a href=\"/crr/admin/users\">Utilizadores</a>\n <a href=\"/crr/admin/notifications\">Registo de e-mails</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/crr/account\" title=\"A minha conta\">\n <span class=\"name-text\">Administrador de teste</span>\n <span class=\"muted\">@phase4-admin</span>\n </a>\n <form method=\"post\" action=\"/crr/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n <div class=\"flash info\" role=\"status\">Troca revertida: os dois turnos voltaram a quem os tinha.</div>\n \n \n<div class=\"page-head\">\n <div>\n <h1>Equipas</h1>\n <p class=\"muted\">Quem entra na rota. Uma equipa é quem fica com um turno, e pode ser uma pessoa ou várias. Os utilizadores que entram na aplicação criam-se em <a href=\"/crr/admin/users\">Utilizadores</a>; aqui escolhe-se apenas quem entra para cada equipa.</p>\n </div>\n</div>\n<section class=\"panel\">\n <div class=\"row between wrap gap\">\n <h2>Adicionar equipa</h2>\n <button class=\"primary\" type=\"submit\" form=\"add-team\">Adicionar equipa</button>\n </div>\n <form id=\"add-team\" method=\"post\" action=\"/crr/admin/teams\" class=\"form-grid\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <div class=\"form-col\">\n <label>Nome<input name=\"name\" required></label>\n <label>Telefone<input name=\"phone\"></label>\n <label>E-mail<input type=\"email\" name=\"email\"></label>\n </div>\n <fieldset class=\"form-col\">\n <legend>Utilizador <span class=\"muted\">(opcional)</span></legend>\n <label>Entra com\n <select name=\"assigned_user_id\">\n <option value=\"\">Ninguém — só aparece na escala</option>\n <option value=\"2\">@member-a — Membro a</option><option value=\"3\">@member-b — Membro b</option><option value=\"1\">@phase4-admin — Administrador de teste</option>\n </select>\n </label>\n </fieldset>\n <label class=\"check\"><input type=\"checkbox\" name=\"notify_email\" checked> Notificações por e-mail</label>\n </form>\n</section>\n<div class=\"stack gap-lg top-space\">\n\n<details class=\"panel\">\n <summary><strong>Administrador de teste</strong> <span class=\"muted\">@phase4-admin</span></summary>\n <form method=\"post\" action=\"/crr/admin/teams/1/update\" class=\"form-grid top-space\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <div class=\"form-col\">\n <label>Nome<input name=\"name\" value=\"Administrador de teste\" required></label>\n <label>Telefone<input name=\"phone\" value=\"\"></label>\n <label>E-mail<input type=\"email\" name=\"email\" value=\"admin@example.com\"></label>\n </div>\n <fieldset class=\"form-col\">\n <legend>Utilizador</legend>\n <label>Entra com\n <select name=\"assigned_user_id\">\n <option value=\"\">Ninguém — só aparece na escala</option>\n <option value=\"2\" >@member-a — Membro a</option><option value=\"3\" >@member-b — Membro b</option><option value=\"1\" selected>@phase4-admin — Administrador de teste</option>\n </select>\n </label>\n <p class=\"muted\">O mesmo utilizador pode entrar em várias equipas: vê e controla os turnos de todas elas.</p>\n </fieldset>\n <label class=\"check\"><input type=\"checkbox\" name=\"notify_email\" checked> Notificações por e-mail</label>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_active\" checked> Ativa</label>\n <div><button class=\"primary\" type=\"submit\">Guardar</button></div>\n \n </form>\n</details>\n\n<details class=\"panel\">\n <summary><strong>Equipa Norte</strong> <span class=\"muted\">@member-a · partilhada com 1</span></summary>\n <form method=\"post\" action=\"/crr/admin/teams/2/update\" class=\"form-grid top-space\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <div class=\"form-col\">\n <label>Nome<input name=\"name\" value=\"Equipa Norte\" required></label>\n <label>Telefone<input name=\"phone\" value=\"\"></label>\n <label>E-mail<input type=\"email\" name=\"email\" value=\"a@example.com\"></label>\n </div>\n <fieldset class=\"form-col\">\n <legend>Utilizador</legend>\n <label>Entra com\n <select name=\"assigned_user_id\">\n <option value=\"\">Ninguém — só aparece na escala</option>\n <option value=\"2\" selected>@member-a — Membro a</option><option value=\"3\" >@member-b — Membro b</option><option value=\"1\" >@phase4-admin — Administrador de teste</option>\n </select>\n </label>\n <p class=\"muted\">O mesmo utilizador pode entrar em várias equipas: vê e controla os turnos de todas elas.</p>\n </fieldset>\n <label class=\"check\"><input type=\"checkbox\" name=\"notify_email\" checked> Notificações por e-mail</label>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_active\" checked> Ativa</label>\n <div><button class=\"primary\" type=\"submit\">Guardar</button></div>\n \n </form>\n</details>\n\n<details class=\"panel\">\n <summary><strong>Equipa Sul</strong> <span class=\"muted\">@member-b</span></summary>\n <form method=\"post\" action=\"/crr/admin/teams/3/update\" class=\"form-grid top-space\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <div class=\"form-col\">\n <label>Nome<input name=\"name\" value=\"Equipa Sul\" required></label>\n <label>Telefone<input name=\"phone\" value=\"\"></label>\n <label>E-mail<input type=\"email\" name=\"email\" value=\"b@example.com\"></label>\n </div>\n <fieldset class=\"form-col\">\n <legend>Utilizador</legend>\n <label>Entra com\n <select name=\"assigned_user_id\">\n <option value=\"\">Ninguém — só aparece na escala</option>\n <option value=\"2\" >@member-a — Membro a</option><option value=\"3\" selected>@member-b — Membro b</option><option value=\"1\" >@phase4-admin — Administrador de teste</option>\n </select>\n </label>\n <p class=\"muted\">O mesmo utilizador pode entrar em várias equipas: vê e controla os turnos de todas elas.</p>\n </fieldset>\n <label class=\"check\"><input type=\"checkbox\" name=\"notify_email\" checked> Notificações por e-mail</label>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_active\" checked> Ativa</label>\n <div><button class=\"primary\" type=\"submit\">Guardar</button></div>\n \n </form>\n</details>\n\n<details class=\"panel\">\n <summary><strong>Equipa partilhada</strong> <span class=\"muted\">@member-a · partilhada com 1</span></summary>\n <form method=\"post\" action=\"/crr/admin/teams/4/update\" class=\"form-grid top-space\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <div class=\"form-col\">\n <label>Nome<input name=\"name\" value=\"Equipa partilhada\" required></label>\n <label>Telefone<input name=\"phone\" value=\"\"></label>\n <label>E-mail<input type=\"email\" name=\"email\" value=\"c@example.com\"></label>\n </div>\n <fieldset class=\"form-col\">\n <legend>Utilizador</legend>\n <label>Entra com\n <select name=\"assigned_user_id\">\n <option value=\"\">Ninguém — só aparece na escala</option>\n <option value=\"2\" selected>@member-a — Membro a</option><option value=\"3\" >@member-b — Membro b</option><option value=\"1\" >@phase4-admin — Administrador de teste</option>\n </select>\n </label>\n <p class=\"muted\">O mesmo utilizador pode entrar em várias equipas: vê e controla os turnos de todas elas.</p>\n </fieldset>\n <label class=\"check\"><input type=\"checkbox\" name=\"notify_email\" checked> Notificações por e-mail</label>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_active\" checked> Ativa</label>\n <div><button class=\"primary\" type=\"submit\">Guardar</button></div>\n <div><a class=\"button danger\" href=\"/crr/admin/teams/4/delete\">Remover</a></div>\n </form>\n</details>\n\n</div>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/admin/teams",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Utilizadores · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/crr/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/crr/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/crr/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/crr/swaps\">Trocas</a>\n \n \n <a href=\"/crr/admin/schedules\">Gerir escalas</a>\n <a href=\"/crr/admin/teams\">Equipas</a>\n <a href=\"/crr/admin/users\">Utilizadores</a>\n <a href=\"/crr/admin/notifications\">Registo de e-mails</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/crr/account\" title=\"A minha conta\">\n <span class=\"name-text\">Administrador de teste</span>\n <span class=\"muted\">@phase4-admin</span>\n </a>\n <form method=\"post\" action=\"/crr/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n<div class=\"page-head\">\n <div>\n <h1>Utilizadores</h1>\n <p class=\"muted\">Cada utilizador tem utilizador e palavra-passe e pode entrar em várias equipas. Quem entra vê e controla os turnos de todas elas, e a administração é do utilizador, não da equipa.</p>\n </div>\n</div>\n<section class=\"panel\">\n <div class=\"row between wrap gap\">\n <h2>Adicionar utilizador</h2>\n <button class=\"primary\" type=\"submit\" form=\"add-user\">Adicionar utilizador</button>\n </div>\n <form id=\"add-user\" method=\"post\" action=\"/crr/admin/users\" class=\"form-grid\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <div class=\"form-col\">\n <label>Nome <span class=\"muted\">(ex.: Equipa de sábado)</span><input name=\"name\" required></label>\n <label>Utilizador<input name=\"username\" pattern=\"[A-Za-z0-9._-]+\" required></label>\n <label>Palavra-passe<input type=\"password\" name=\"password\" minlength=\"4\" required></label>\n </div>\n <fieldset class=\"form-col\">\n <legend>Equipas <span class=\"muted\">(pode escolher várias)</span></legend>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"1\"> Administrador de teste</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"2\"> Equipa Norte</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"3\"> Equipa Sul</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"4\"> Equipa partilhada</label>\n \n </fieldset>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_admin\"> Administrador</label>\n </form>\n</section>\n<div class=\"stack gap-lg top-space\">\n\n<details class=\"panel\">\n <summary><strong>@member-a</strong> <span class=\"muted\">Membro a · 2 equipa(s)</span></summary>\n <form method=\"post\" action=\"/crr/admin/users/2/update\" class=\"form-grid top-space\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <div class=\"form-col\">\n <label>Nome<input name=\"name\" value=\"Membro a\" required></label>\n <label>Utilizador<input name=\"username\" pattern=\"[A-Za-z0-9._-]+\" value=\"member-a\" required></label>\n <label>Nova palavra-passe <span class=\"muted\">(vazio mantém a atual)</span><input type=\"password\" name=\"password\" minlength=\"4\"></label>\n </div>\n <fieldset class=\"form-col\">\n <legend>Equipas que entra</legend>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"1\" > Administrador de teste</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"2\" checked> Equipa Norte</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"3\" > Equipa Sul</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"4\" checked> Equipa partilhada</label>\n \n </fieldset>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_admin\" > Administrador</label>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_active\" checked> Ativo</label>\n <div><button class=\"primary\" type=\"submit\">Guardar</button></div>\n \n </form>\n \n <p class=\"muted\">Equipas: Equipa Norte, Equipa partilhada</p>\n \n</details>\n\n<details class=\"panel\">\n <summary><strong>@member-b</strong> <span class=\"muted\">Membro b · 1 equipa(s)</span></summary>\n <form method=\"post\" action=\"/crr/admin/users/3/update\" class=\"form-grid top-space\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <div class=\"form-col\">\n <label>Nome<input name=\"name\" value=\"Membro b\" required></label>\n <label>Utilizador<input name=\"username\" pattern=\"[A-Za-z0-9._-]+\" value=\"member-b\" required></label>\n <label>Nova palavra-passe <span class=\"muted\">(vazio mantém a atual)</span><input type=\"password\" name=\"password\" minlength=\"4\"></label>\n </div>\n <fieldset class=\"form-col\">\n <legend>Equipas que entra</legend>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"1\" > Administrador de teste</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"2\" > Equipa Norte</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"3\" checked> Equipa Sul</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"4\" > Equipa partilhada</label>\n \n </fieldset>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_admin\" > Administrador</label>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_active\" checked> Ativo</label>\n <div><button class=\"primary\" type=\"submit\">Guardar</button></div>\n \n </form>\n \n <p class=\"muted\">Equipas: Equipa Sul</p>\n \n</details>\n\n<details class=\"panel\">\n <summary><strong>@phase4-admin</strong> <span class=\"muted\">Administrador de teste · administrador · 1 equipa(s)</span></summary>\n <form method=\"post\" action=\"/crr/admin/users/1/update\" class=\"form-grid top-space\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <div class=\"form-col\">\n <label>Nome<input name=\"name\" value=\"Administrador de teste\" required></label>\n <label>Utilizador<input name=\"username\" pattern=\"[A-Za-z0-9._-]+\" value=\"phase4-admin\" required></label>\n <label>Nova palavra-passe <span class=\"muted\">(vazio mantém a atual)</span><input type=\"password\" name=\"password\" minlength=\"4\"></label>\n </div>\n <fieldset class=\"form-col\">\n <legend>Equipas que entra</legend>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"1\" checked> Administrador de teste</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"2\" > Equipa Norte</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"3\" > Equipa Sul</label>\n \n <label class=\"check\"><input type=\"checkbox\" name=\"team_ids\" value=\"4\" > Equipa partilhada</label>\n \n </fieldset>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_admin\" checked> Administrador</label>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_active\" checked> Ativo</label>\n <div><button class=\"primary\" type=\"submit\">Guardar</button></div>\n \n </form>\n \n <p class=\"muted\">Equipas: Administrador de teste</p>\n \n</details>\n\n</div>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/admin/users",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/crr/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/crr/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/crr/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/crr/swaps\">Trocas</a>\n \n \n <a href=\"/crr/admin/schedules\">Gerir escalas</a>\n <a href=\"/crr/admin/teams\">Equipas</a>\n <a href=\"/crr/admin/users\">Utilizadores</a>\n <a href=\"/crr/admin/notifications\">Registo de e-mails</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/crr/account\" title=\"A minha conta\">\n <span class=\"name-text\">Administrador de teste</span>\n <span class=\"muted\">@phase4-admin</span>\n </a>\n <form method=\"post\" action=\"/crr/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n<div class=\"page-head\">\n <div>\n <h1>Escalas</h1>\n <p class=\"muted\">Uma página por tipo de escala. A escala da noite é fixa e repete todos os meses; o almoço de sábado e o de domingo são rotações independentes.</p>\n </div>\n <a class=\"button primary\" href=\"/crr/admin/assign\">Atribuir por dia</a>\n</div>\n<div class=\"stack gap\">\n\n<section class=\"panel\" id=\"export-pdf\">\n <h2>Exportar PDF</h2>\n <p class=\"muted\">Escolhe uma escala e as datas para descarregar a lista de dias e equipas numa única folha A4.</p>\n \n<form class=\"stack\" method=\"get\" action=\"/crr/export/schedule.pdf\">\n <label>Escala\n <select name=\"schedule_id\" required>\n \n <option value=\"1\" selected>Noite</option>\n \n <option value=\"2\" >Almoço - Sábados</option>\n \n <option value=\"3\" >Almoço - Domingo</option>\n \n <option value=\"4\" >Rotação Phase 4</option>\n \n </select>\n </label>\n <div class=\"form-grid\">\n <label>Data inicial\n <input type=\"date\" name=\"start_date\" value=\"2026-10-01\" min=\"2000-01-01\" max=\"2100-12-31\" required>\n </label>\n <label>Data final\n <input type=\"date\" name=\"end_date\" value=\"2026-10-31\" min=\"2000-01-01\" max=\"2100-12-31\" required>\n </label>\n </div>\n <p class=\"muted\">As datas inicial e final estão incluídas. Máximo de 12 meses; o tamanho do texto adapta-se ao intervalo.</p>\n <div class=\"row end\"><button class=\"primary\" type=\"submit\">Descarregar PDF</button></div>\n</form>\n</section>\n\n\n\n<section class=\"panel\">\n <div class=\"row between wrap gap\">\n <div>\n <h2>Noite</h2>\n <p class=\"muted\">Todos os dias · repete todos os meses · 20:30–00:00</p>\n </div>\n <div class=\"actions\">\n <a class=\"button secondary\" href=\"/crr/admin/schedules?schedule_id=1#export-pdf\">Exportar PDF</a>\n <a class=\"button primary\" href=\"/crr/admin/schedules/1\">Configurar</a>\n </div>\n </div>\n \n <h3>Padrão</h3>\n <ul class=\"link-list\">\n <li class=\"muted\">Ainda não há ninguém definido.</li>\n </ul>\n \n</section>\n\n\n<section class=\"panel\">\n <div class=\"row between wrap gap\">\n <div>\n <h2>Almoço - Sábados</h2>\n <p class=\"muted\">Rotação · Sábado · 13:00–16:00</p>\n </div>\n <div class=\"actions\">\n <a class=\"button secondary\" href=\"/crr/admin/schedules?schedule_id=2#export-pdf\">Exportar PDF</a>\n <a class=\"button primary\" href=\"/crr/admin/schedules/2\">Configurar</a>\n </div>\n </div>\n \n <p class=\"muted\">Próximas datas</p>\n <p class=\"date-list\">sáb 10 out · sáb 17 out · sáb 24 out · sáb 31 out · sáb 07 nov · sáb 14 nov</p>\n <h3>Ordem da rotação</h3>\n <ol class=\"rotation-list\">\n <li class=\"muted\">Ainda não há membros.</li>\n </ol>\n \n</section>\n\n\n<section class=\"panel\">\n <div class=\"row between wrap gap\">\n <div>\n <h2>Almoço - Domingo</h2>\n <p class=\"muted\">Rotação · Domingo · 13:00–16:00</p>\n </div>\n <div class=\"actions\">\n <a class=\"button secondary\" href=\"/crr/admin/schedules?schedule_id=3#export-pdf\">Exportar PDF</a>\n <a class=\"button primary\" href=\"/crr/admin/schedules/3\">Configurar</a>\n </div>\n </div>\n \n <p class=\"muted\">Próximas datas</p>\n <p class=\"date-list\">dom 11 out · dom 18 out · dom 25 out · dom 01 nov · dom 08 nov · dom 15 nov</p>\n <h3>Ordem da rotação</h3>\n <ol class=\"rotation-list\">\n <li class=\"muted\">Ainda não há membros.</li>\n </ol>\n \n</section>\n\n\n<section class=\"panel\">\n <div class=\"row between wrap gap\">\n <div>\n <h2>Rotação Phase 4</h2>\n <p class=\"muted\">Rotação · Sábado · 12:00–14:00</p>\n </div>\n <div class=\"actions\">\n <a class=\"button secondary\" href=\"/crr/admin/schedules?schedule_id=4#export-pdf\">Exportar PDF</a>\n <a class=\"button primary\" href=\"/crr/admin/schedules/4\">Configurar</a>\n </div>\n </div>\n \n <p class=\"muted\">Próximas datas</p>\n <p class=\"date-list\">sáb 10 out · sáb 17 out · sáb 24 out · sáb 31 out · sáb 07 nov · sáb 14 nov</p>\n <h3>Ordem da rotação</h3>\n <ol class=\"rotation-list\">\n \n <li><span>Equipa Norte</span></li>\n \n <li><span>Equipa Sul</span></li>\n \n </ol>\n \n</section>\n\n<section class=\"panel\">\n <h2>Calendário da equipa</h2>\n <p class=\"muted\">Um único endereço com a rota completa, para qualquer calendário.</p>\n <p><a class=\"button secondary\" href=\"/crr/calendar\">Adicionar ao calendário do telemóvel</a></p>\n <div class=\"inline-form\">\n <input type=\"text\" readonly value=\"http://testserver/crr/calendar/phase4-whole-rota.ics\" aria-label=\"Endereço do calendário da equipa\">\n <button class=\"secondary\" type=\"button\" data-copy=\"http://testserver/crr/calendar/phase4-whole-rota.ics\">Copiar</button>\n </div>\n</section>\n</div>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/admin/schedules",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/crr/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/crr/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/crr/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/crr/swaps\">Trocas</a>\n \n \n <a href=\"/crr/admin/schedules\">Gerir escalas</a>\n <a href=\"/crr/admin/teams\">Equipas</a>\n <a href=\"/crr/admin/users\">Utilizadores</a>\n <a href=\"/crr/admin/notifications\">Registo de e-mails</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/crr/account\" title=\"A minha conta\">\n <span class=\"name-text\">Administrador de teste</span>\n <span class=\"muted\">@phase4-admin</span>\n </a>\n <form method=\"post\" action=\"/crr/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n\n<div class=\"page-head\">\n <div>\n <p class=\"eyebrow\"><a href=\"/crr/admin/schedules\">Escalas</a></p>\n <h1>Rotação Phase 4</h1>\n <p class=\"muted\">Rotação · Sábado · 12:00–14:00</p>\n </div>\n <form method=\"post\" action=\"/crr/admin/schedules/4/settings\" class=\"inline-form\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <label>Das horas<input type=\"time\" name=\"start_time\" value=\"12:00\" required></label>\n <label>Às horas<input type=\"time\" name=\"end_time\" value=\"14:00\" required></label>\n <label class=\"check\"><input type=\"checkbox\" name=\"requires_manager_approval\" checked> Aprovação da gestão</label>\n <label class=\"check\"><input type=\"checkbox\" name=\"is_active\" checked> Ativa</label>\n <button class=\"secondary\" type=\"submit\">Guardar</button>\n </form>\n</div>\n<div class=\"stack gap-lg\">\n\n <section class=\"panel\">\n <div class=\"row between wrap gap\">\n <div><h3>Janeiro 2030</h3></div>\n <div class=\"month-nav\">\n <a class=\"button secondary\" href=\"/crr/admin/schedules/4?year=2029&amp;month=12\">← Anterior</a>\n <a class=\"button secondary\" href=\"/crr/admin/schedules/4?year=2030&amp;month=2\">Seguinte →</a>\n </div>\n </div>\n <form method=\"post\" action=\"/crr/admin/schedules/4/apply-rotation\" class=\"inline-form\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <input type=\"hidden\" name=\"year\" value=\"2030\"><input type=\"hidden\" name=\"month\" value=\"1\">\n <button class=\"secondary\" type=\"submit\">Aplicar rotação em Janeiro</button>\n </form>\n <p class=\"muted\">Deixa sem atribuição os turnos <strong>automáticos</strong> do mês e ano escolhidos e de todos os seguintes. Nenhum turno é apagado e as atribuições manuais e as trocas concluídas são mantidas. Use Limpar turnos antes de Aplicar rotação, para recalcular tudo a partir de um ponto.</p>\n <form method=\"post\" action=\"/crr/admin/schedules/4/assignments\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <input type=\"hidden\" name=\"year\" value=\"2030\"><input type=\"hidden\" name=\"month\" value=\"1\">\n <div class=\"table-wrap\"><table><thead><tr><th>Data</th><th>Equipa</th><th>Origem</th><th>Ação</th></tr></thead><tbody>\n \n <tr><td>sáb 05 jan</td><td><select name=\"assignment_1\"><option value=\"\">Sem atribuição</option><option value=\"1\" >Administrador de teste</option><option value=\"2\" selected>Equipa Norte</option><option value=\"3\" >Equipa Sul</option><option value=\"4\" >Equipa partilhada</option></select></td><td><span class=\"badge\">manual</span></td><td><button class=\"secondary small\" type=\"submit\" form=\"regenerate-1\" aria-label=\"Recalcular o turno de sáb 05 jan\">Recalcular</button></td></tr>\n \n <tr><td>sáb 12 jan</td><td><select name=\"assignment_2\"><option value=\"\">Sem atribuição</option><option value=\"1\" >Administrador de teste</option><option value=\"2\" >Equipa Norte</option><option value=\"3\" selected>Equipa Sul</option><option value=\"4\" >Equipa partilhada</option></select></td><td><span class=\"badge\">manual</span></td><td><button class=\"secondary small\" type=\"submit\" form=\"regenerate-2\" aria-label=\"Recalcular o turno de sáb 12 jan\">Recalcular</button></td></tr>\n \n <tr><td>sáb 19 jan</td><td><select name=\"assignment_3\"><option value=\"\">Sem atribuição</option><option value=\"1\" >Administrador de teste</option><option value=\"2\" selected>Equipa Norte</option><option value=\"3\" >Equipa Sul</option><option value=\"4\" >Equipa partilhada</option></select></td><td><span class=\"badge\">troca</span></td><td><button class=\"secondary small\" type=\"submit\" form=\"regenerate-3\" aria-label=\"Recalcular o turno de sáb 19 jan\">Recalcular</button></td></tr>\n \n <tr><td>sáb 26 jan</td><td><select name=\"assignment_4\"><option value=\"\">Sem atribuição</option><option value=\"1\" >Administrador de teste</option><option value=\"2\" >Equipa Norte</option><option value=\"3\" selected>Equipa Sul</option><option value=\"4\" >Equipa partilhada</option></select></td><td><span class=\"badge\">automático</span></td><td><button class=\"secondary small\" type=\"submit\" form=\"regenerate-4\" aria-label=\"Recalcular o turno de sáb 26 jan\">Recalcular</button></td></tr>\n \n </tbody></table></div>\n <div class=\"sticky-actions\"><button class=\"primary\" type=\"submit\">Guardar Janeiro</button></div>\n </form>\n \n <form id=\"regenerate-1\" method=\"post\" action=\"/crr/admin/schedules/4/assignments/1/regenerate\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n </form>\n \n <form id=\"regenerate-2\" method=\"post\" action=\"/crr/admin/schedules/4/assignments/2/regenerate\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n </form>\n \n <form id=\"regenerate-3\" method=\"post\" action=\"/crr/admin/schedules/4/assignments/3/regenerate\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n </form>\n \n <form id=\"regenerate-4\" method=\"post\" action=\"/crr/admin/schedules/4/assignments/4/regenerate\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n </form>\n \n </section>\n <section class=\"panel\">\n <h3>Próximas datas</h3>\n <p class=\"date-list\">sáb 10 out · sáb 17 out · sáb 24 out · sáb 31 out · sáb 07 nov · sáb 14 nov</p>\n </section>\n <section class=\"panel\" id=\"rotation-order\">\n <h3>Ordem da rotação</h3>\n <p class=\"muted\">Rotação independente, preenchida por ordem. Depois de alterar a ordem, use Aplicar rotação acima para atualizar os turnos automáticos deste mês.</p>\n <ol class=\"rotation-list\">\n \n <li>\n <span>Equipa Norte</span>\n <div class=\"actions compact\">\n <form method=\"post\" action=\"/crr/admin/rotation/1/move\"><input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\"><input type=\"hidden\" name=\"direction\" value=\"up\"><button class=\"secondary small\" type=\"submit\" aria-label=\"Subir Equipa Norte\">↑</button></form>\n <form method=\"post\" action=\"/crr/admin/rotation/1/move\"><input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\"><input type=\"hidden\" name=\"direction\" value=\"down\"><button class=\"secondary small\" type=\"submit\" aria-label=\"Descer Equipa Norte\">↓</button></form>\n <form method=\"post\" action=\"/crr/admin/rotation/1/remove\"><input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\"><button class=\"danger small\" type=\"submit\">Remover</button></form>\n </div>\n </li>\n \n <li>\n <span>Equipa Sul</span>\n <div class=\"actions compact\">\n <form method=\"post\" action=\"/crr/admin/rotation/2/move\"><input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\"><input type=\"hidden\" name=\"direction\" value=\"up\"><button class=\"secondary small\" type=\"submit\" aria-label=\"Subir Equipa Sul\">↑</button></form>\n <form method=\"post\" action=\"/crr/admin/rotation/2/move\"><input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\"><input type=\"hidden\" name=\"direction\" value=\"down\"><button class=\"secondary small\" type=\"submit\" aria-label=\"Descer Equipa Sul\">↓</button></form>\n <form method=\"post\" action=\"/crr/admin/rotation/2/remove\"><input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\"><button class=\"danger small\" type=\"submit\">Remover</button></form>\n </div>\n </li>\n \n </ol>\n <form method=\"post\" action=\"/crr/admin/schedules/4/rotation\" class=\"inline-form\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <select name=\"team_id\" required><option value=\"\">Adicionar equipa…</option><option value=\"1\">Administrador de teste</option><option value=\"2\">Equipa Norte</option><option value=\"3\">Equipa Sul</option><option value=\"4\">Equipa partilhada</option></select>\n <button class=\"primary\" type=\"submit\">Adicionar à rotação</button>\n </form>\n </section>\n\n <section class=\"panel\">\n <h3>Limpar atribuições</h3>\n <p class=\"muted\">Limpar atribuições a partir do mês de:</p>\n <form method=\"post\" action=\"/crr/admin/schedules/4/clear-from\" class=\"inline-form\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <label><select name=\"month\">\n <option value=\"1\" selected>Janeiro</option><option value=\"2\" >Fevereiro</option><option value=\"3\" >Março</option><option value=\"4\" >Abril</option><option value=\"5\" >Maio</option><option value=\"6\" >Junho</option><option value=\"7\" >Julho</option><option value=\"8\" >Agosto</option><option value=\"9\" >Setembro</option><option value=\"10\" >Outubro</option><option value=\"11\" >Novembro</option><option value=\"12\" >Dezembro</option>\n </select></label>\n <label><input type=\"number\" name=\"year\" value=\"2030\" min=\"2000\" max=\"2100\" required></label>\n <button class=\"danger\" type=\"submit\">Limpar turnos</button>\n </form>\n </section>\n <section class=\"panel\" id=\"swap-requests\">\n <h3>Pedidos de troca</h3>\n <p class=\"muted\">Um administrador pode atribuir o turno a outra equipa, mesmo que nenhuma pessoa entre na aplicação por ela. Só se aplica se quem pediu a troca continuar a ser o titular do turno.</p>\n <ul class=\"link-list\">\n \n <li class=\"muted\">Não há pedidos de troca nesta escala.</li>\n \n </ul>\n </section>\n</div>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/admin/schedules/4?year=2030&month=1",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Atribuição por dia · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/crr/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/crr/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/crr/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/crr/swaps\">Trocas</a>\n \n \n <a href=\"/crr/admin/schedules\">Gerir escalas</a>\n <a href=\"/crr/admin/teams\">Equipas</a>\n <a href=\"/crr/admin/users\">Utilizadores</a>\n <a href=\"/crr/admin/notifications\">Registo de e-mails</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/crr/account\" title=\"A minha conta\">\n <span class=\"name-text\">Administrador de teste</span>\n <span class=\"muted\">@phase4-admin</span>\n </a>\n <form method=\"post\" action=\"/crr/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n<div class=\"page-head\">\n <div>\n <p class=\"eyebrow\"><a href=\"/crr/admin/schedules\">Escalas</a></p>\n <h1>Atribuição por dia</h1>\n <p class=\"muted\">Escolha o dia e a equipa que fica com cada turno. É uma atribuição direta, não uma troca.</p>\n </div>\n <form method=\"get\" action=\"/crr/admin/assign\" class=\"inline-form\">\n <label>Dia<input type=\"date\" name=\"on\" value=\"2030-01-05\" min=\"1999-12-31\" max=\"2100-12-30\" required onchange=\"this.form.submit()\"></label>\n </form>\n</div>\n\n<div class=\"month-nav\">\n <a class=\"button secondary\" href=\"/crr/admin/assign?on=2030-01-04\">← Dia anterior</a>\n <a class=\"button secondary \" href=\"/crr/admin/assign?on=2026-10-07\">Hoje</a>\n <a class=\"button secondary\" href=\"/crr/admin/assign?on=2030-01-06\">Dia seguinte →</a>\n</div>\n\n<div class=\"stack gap-lg top-space\">\n <section class=\"panel\">\n <h2>Sábado, 5 de janeiro de 2030</h2>\n \n <form method=\"post\" action=\"/crr/admin/assign\" class=\"row between wrap gap top-space\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <input type=\"hidden\" name=\"assignment_id\" value=\"9\">\n <div>\n <strong>Noite</strong>\n <span class=\"muted\"> · 20:30–00:00</span>\n <div class=\"muted\">\n \n Agora: sem atribuição\n \n </div>\n </div>\n <div class=\"inline-form\">\n <select name=\"team_id\">\n <option value=\"auto\" selected>Automático → sem ninguém</option>\n <option value=\"\" >Sem atribuição</option>\n \n <option value=\"1\" >Administrador de teste</option>\n \n <option value=\"2\" >Equipa Norte</option>\n \n <option value=\"3\" >Equipa Sul</option>\n \n <option value=\"4\" >Equipa partilhada</option>\n \n </select>\n <button class=\"secondary small\" type=\"submit\">Atribuir</button>\n </div>\n </form>\n \n <form method=\"post\" action=\"/crr/admin/assign\" class=\"row between wrap gap top-space\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <input type=\"hidden\" name=\"assignment_id\" value=\"36\">\n <div>\n <strong>Almoço - Sábados</strong>\n <span class=\"muted\"> · 13:00–16:00</span>\n <div class=\"muted\">\n \n Agora: sem atribuição\n \n </div>\n </div>\n <div class=\"inline-form\">\n <select name=\"team_id\">\n <option value=\"auto\" selected>Automático → sem ninguém</option>\n <option value=\"\" >Sem atribuição</option>\n \n <option value=\"1\" >Administrador de teste</option>\n \n <option value=\"2\" >Equipa Norte</option>\n \n <option value=\"3\" >Equipa Sul</option>\n \n <option value=\"4\" >Equipa partilhada</option>\n \n </select>\n <button class=\"secondary small\" type=\"submit\">Atribuir</button>\n </div>\n </form>\n \n <form method=\"post\" action=\"/crr/admin/assign\" class=\"row between wrap gap top-space\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <input type=\"hidden\" name=\"assignment_id\" value=\"1\">\n <div>\n <strong>Rotação Phase 4</strong>\n <span class=\"muted\"> · 12:00–14:00</span>\n <div class=\"muted\">\n \n Agora: Equipa Norte (manual)\n \n </div>\n </div>\n <div class=\"inline-form\">\n <select name=\"team_id\">\n <option value=\"auto\" >Automático → Equipa Norte</option>\n <option value=\"\" >Sem atribuição</option>\n \n <option value=\"1\" >Administrador de teste</option>\n \n <option value=\"2\" selected>Equipa Norte</option>\n \n <option value=\"3\" >Equipa Sul</option>\n \n <option value=\"4\" >Equipa partilhada</option>\n \n </select>\n <button class=\"secondary small\" type=\"submit\">Atribuir</button>\n </div>\n </form>\n \n </section>\n <p class=\"muted\">Atribuir aqui fecha qualquer pedido de troca em aberto sobre esse turno e avisa a nova equipa e a anterior.</p>\n <p class=\"muted\">«Automático» repõe a equipa que a escala dá a este dia — a ordem da rotação ou o padrão de noites — e volta a marcar o turno como automático.</p>\n</div>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/admin/assign?on=2030-01-05",
"status": 200
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Registo de e-mails · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/crr/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/crr/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/crr/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/crr/swaps\">Trocas</a>\n \n \n <a href=\"/crr/admin/schedules\">Gerir escalas</a>\n <a href=\"/crr/admin/teams\">Equipas</a>\n <a href=\"/crr/admin/users\">Utilizadores</a>\n <a href=\"/crr/admin/notifications\">Registo de e-mails</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/crr/account\" title=\"A minha conta\">\n <span class=\"name-text\">Administrador de teste</span>\n <span class=\"muted\">@phase4-admin</span>\n </a>\n <form method=\"post\" action=\"/crr/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n \n<div class=\"page-head\"><div><h1>Registo de e-mails</h1><p class=\"muted\">«ignorado» significa que a equipa não tem e-mail, que tem as notificações desligadas ou que o SMTP não está configurado.</p></div></div>\n<div class=\"table-wrap\"><table><thead><tr><th>Data e hora</th><th>Evento</th><th>Equipa</th><th>Destinatário</th><th>Estado</th><th>Assunto</th></tr></thead><tbody>\n<tr><td>07/10/2026 10:58</td><td>Troca revertida</td><td>Equipa Sul</td><td>b@example.com</td><td><span class=\"status status-skipped\">ignorado</span></td><td>Troca de turno revertida</td></tr><tr><td>07/10/2026 10:58</td><td>Troca revertida</td><td>Equipa Norte</td><td>a@example.com</td><td><span class=\"status status-skipped\">ignorado</span></td><td>Troca de turno revertida</td></tr><tr><td>07/10/2026 10:58</td><td>Troca aprovada</td><td>Equipa Sul</td><td>b@example.com</td><td><span class=\"status status-skipped\">ignorado</span></td><td>Troca de turno aprovada</td></tr><tr><td>07/10/2026 10:58</td><td>Troca aprovada</td><td>Equipa Norte</td><td>a@example.com</td><td><span class=\"status status-skipped\">ignorado</span></td><td>Troca de turno aprovada</td></tr><tr><td>07/10/2026 10:58</td><td>Troca aceite</td><td>Equipa Norte</td><td>a@example.com</td><td><span class=\"status status-skipped\">ignorado</span></td><td>A sua troca de turno foi aceite</td></tr><tr><td>07/10/2026 10:58</td><td>Troca aguarda aprovação</td><td>Administrador de teste</td><td>admin@example.com</td><td><span class=\"status status-skipped\">ignorado</span></td><td>Uma troca de turno precisa de aprovação</td></tr><tr><td>07/10/2026 10:58</td><td>Pedido de troca</td><td>Equipa Sul</td><td>b@example.com</td><td><span class=\"status status-skipped\">ignorado</span></td><td>Troca de turnos: sáb 05 jan por sáb 12 jan – Rotação Phase 4</td></tr>\n</tbody></table></div>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/admin/notifications",
"status": 200
},
{
"body": "",
"headers": {
"location": "/crr/admin/schedules"
},
"method": "GET",
"path": "/admin/month",
"status": 303
},
{
"body": "",
"headers": {
"location": "/crr/admin/schedules/4?year=2030&month=1"
},
"method": "POST",
"path": "/admin/schedules/4/apply-rotation",
"status": 303
},
{
"body": "",
"headers": {
"location": "/crr/admin/schedules/4?year=2030&month=1"
},
"method": "POST",
"path": "/admin/schedules/4/clear-from",
"status": 303
},
{
"body": "",
"headers": {
"location": "/crr/admin/assign?on=2030-01-26"
},
"method": "POST",
"path": "/admin/assign",
"status": 303
},
{
"body": "",
"headers": {
"location": "/crr/admin/teams"
},
"method": "POST",
"path": "/admin/teams",
"status": 303
},
{
"body": "",
"headers": {
"location": "/crr/admin/teams"
},
"method": "POST",
"path": "/admin/teams/5/update",
"status": 303
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>Remover Equipa editada · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/crr/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/crr/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/crr/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/crr/swaps\">Trocas</a>\n \n \n <a href=\"/crr/admin/schedules\">Gerir escalas</a>\n <a href=\"/crr/admin/teams\">Equipas</a>\n <a href=\"/crr/admin/users\">Utilizadores</a>\n <a href=\"/crr/admin/notifications\">Registo de e-mails</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/crr/account\" title=\"A minha conta\">\n <span class=\"name-text\">Administrador de teste</span>\n <span class=\"muted\">@phase4-admin</span>\n </a>\n <form method=\"post\" action=\"/crr/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n <div class=\"flash info\" role=\"status\">Equipa atualizada.</div>\n \n \n<div class=\"page-head\">\n <div>\n <p class=\"eyebrow\"><a href=\"/crr/admin/teams\">Equipas</a></p>\n <h1>Remover equipa</h1>\n <p class=\"muted\">Isto é definitivo e não pode ser desfeito.</p>\n </div>\n</div>\n<section class=\"panel\">\n <h2>Equipa editada</h2>\n <p>Sem utilizador, apenas aparece na escala.</p>\n <p class=\"muted\">Só é possível remover quem ainda não tem turnos atribuídos. Primeiro transfira os turnos desta equipa para outras; assim o registo de quem trabalhou cada data mantém-se.</p>\n <p class=\"muted\">Ao remover, acontece o seguinte:</p>\n <ul class=\"link-list\">\n <li>0 posição(ões) saem das rotações.</li>\n \n \n <li>O histórico de e-mails é mantido, sem ligação a esta equipa.</li>\n </ul>\n <p class=\"muted\">Se só quiser que esta equipa deixe de receber turnos, desative-a em vez de a remover.</p>\n <div class=\"actions\">\n <form method=\"post\" action=\"/crr/admin/teams/5/delete\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"danger\" type=\"submit\">Remover definitivamente</button>\n </form>\n <a class=\"button secondary\" href=\"/crr/admin/teams\">Cancelar</a>\n </div>\n</section>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/admin/teams/5/delete",
"status": 200
},
{
"body": "",
"headers": {
"location": "/crr/admin/teams"
},
"method": "POST",
"path": "/admin/teams/5/delete",
"status": 303
},
{
"body": "",
"headers": {
"location": "/crr/admin/users"
},
"method": "POST",
"path": "/admin/users",
"status": 303
},
{
"body": "",
"headers": {
"location": "/crr/admin/users"
},
"method": "POST",
"path": "/admin/users/4/update",
"status": 303
},
{
"body": "",
"headers": {
"location": "/crr/admin/users"
},
"method": "POST",
"path": "/admin/users/4/delete",
"status": 303
},
{
"body": "",
"headers": {
"location": "/crr/"
},
"method": "POST",
"path": "/account",
"status": 303
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>A minha conta · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/crr/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/crr/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/crr/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/crr/swaps\">Trocas</a>\n <a href=\"/crr/export/schedule\">Exportar PDF</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/crr/account\" title=\"A minha conta\">\n <span class=\"name-text\">Membro a</span>\n <span class=\"muted\">@member-a</span>\n </a>\n <form method=\"post\" action=\"/crr/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n <div class=\"flash error\" role=\"status\">A nova palavra-passe é igual à atual.</div>\n \n \n<div class=\"page-head\">\n <div>\n <p class=\"eyebrow\"><a href=\"/crr/\">Rota</a></p>\n <h1>A minha conta</h1>\n <p class=\"muted\">Membro a · @member-a</p>\n \n <p class=\"muted\">Este utilizador entra em nome de 2 equipa(s): Equipa Norte, Equipa partilhada. Vê e controla os turnos de todas.</p>\n \n </div>\n</div>\n\n<section class=\"panel\">\n <h2>Alterar palavra-passe</h2>\n <form method=\"post\" action=\"/crr/account\" class=\"form-grid\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <input type=\"password\" name=\"new_password\" minlength=\"4\" autocomplete=\"new-password\" required autofocus>\n <div><button class=\"primary\" type=\"submit\">Alterar palavra-passe</button></div>\n </form>\n</section>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/account",
"status": 200
},
{
"body": "",
"headers": {
"location": "/crr/"
},
"method": "POST",
"path": "/account",
"status": 303
},
{
"body": "<!doctype html>\n<html lang=\"pt-PT\">\n<head>\n <meta charset=\"utf-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n <meta name=\"theme-color\" content=\"#ed1c24\">\n <title>A minha conta · Contínuos CRR</title>\n <link rel=\"icon\" type=\"image/png\" href=\"http://testserver/crr/brand/assets/favicon.png\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/tokens.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/base.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/brand/styles/components.css\">\n <link rel=\"stylesheet\" href=\"http://testserver/crr/static/app.css\">\n</head>\n<body>\n <header class=\"topbar\">\n <div class=\"container topbar-inner\">\n <a class=\"brand\" href=\"/crr/\" aria-label=\"Contínuos CRR — início\">\n <img class=\"brand-logo\" src=\"http://testserver/crr/brand/assets/logo.png\" alt=\"CRR\">\n <span class=\"brand-name\">Contínuos CRR</span>\n </a>\n <nav class=\"nav\" aria-label=\"Navegação principal\">\n \n <a href=\"/crr/swaps\">Trocas</a>\n <a href=\"/crr/export/schedule\">Exportar PDF</a>\n \n \n </nav>\n \n <div class=\"account\">\n <a class=\"account-link\" href=\"/crr/account\" title=\"A minha conta\">\n <span class=\"name-text\">Membro a</span>\n <span class=\"muted\">@member-a</span>\n </a>\n <form method=\"post\" action=\"/crr/logout\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <button class=\"link-button\" type=\"submit\">Sair</button>\n </form>\n </div>\n \n </div>\n </header>\n <main class=\"container\">\n \n <div class=\"flash info\" role=\"status\">Palavra-passe alterada.</div>\n \n \n<div class=\"page-head\">\n <div>\n <p class=\"eyebrow\"><a href=\"/crr/\">Rota</a></p>\n <h1>A minha conta</h1>\n <p class=\"muted\">Membro a · @member-a</p>\n \n <p class=\"muted\">Este utilizador entra em nome de 2 equipa(s): Equipa Norte, Equipa partilhada. Vê e controla os turnos de todas.</p>\n \n </div>\n</div>\n\n<section class=\"panel\">\n <h2>Alterar palavra-passe</h2>\n <form method=\"post\" action=\"/crr/account\" class=\"form-grid\">\n <input type=\"hidden\" name=\"csrf_token\" value=\"<csrf>\">\n <input type=\"password\" name=\"new_password\" minlength=\"4\" autocomplete=\"new-password\" required autofocus>\n <div><button class=\"primary\" type=\"submit\">Alterar palavra-passe</button></div>\n </form>\n</section>\n\n </main>\n</body>\n</html>",
"headers": {
"content-type": "text/html; charset=utf-8"
},
"method": "GET",
"path": "/account",
"status": 200
},
{
"body": "",
"headers": {
"location": "/crr/login"
},
"method": "POST",
"path": "/logout",
"status": 303
},
{
"body": "{\"detail\":\"É necessário iniciar sessão\"}",
"headers": {
"content-type": "application/json"
},
"method": "GET",
"path": "/account",
"status": 401
}
],
"rows": {
"alembic_version": [
{
"version_num": "0001_initial"
}
],
"assignments": [
{
"date": "2030-01-05",
"id": 1,
"note": "Revertida a troca #1 por Administrador de teste",
"schedule_id": 4,
"source": "manual",
"team_id": 2,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-12",
"id": 2,
"note": "Revertida a troca #1 por Administrador de teste",
"schedule_id": 4,
"source": "manual",
"team_id": 3,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-19",
"id": 3,
"note": null,
"schedule_id": 4,
"source": "swap",
"team_id": 2,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-26",
"id": 4,
"note": null,
"schedule_id": 4,
"source": "manual",
"team_id": 3,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-01",
"id": 5,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-02",
"id": 6,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-03",
"id": 7,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-04",
"id": 8,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-05",
"id": 9,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-06",
"id": 10,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-07",
"id": 11,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-08",
"id": 12,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-09",
"id": 13,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-10",
"id": 14,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-11",
"id": 15,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-12",
"id": 16,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-13",
"id": 17,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-14",
"id": 18,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-15",
"id": 19,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-16",
"id": 20,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-17",
"id": 21,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-18",
"id": 22,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-19",
"id": 23,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-20",
"id": 24,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-21",
"id": 25,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-22",
"id": 26,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-23",
"id": 27,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-24",
"id": 28,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-25",
"id": 29,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-26",
"id": 30,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-27",
"id": 31,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-28",
"id": 32,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-29",
"id": 33,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-30",
"id": 34,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-31",
"id": 35,
"note": null,
"schedule_id": 1,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-05",
"id": 36,
"note": null,
"schedule_id": 2,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-12",
"id": 37,
"note": null,
"schedule_id": 2,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-19",
"id": 38,
"note": null,
"schedule_id": 2,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-26",
"id": 39,
"note": null,
"schedule_id": 2,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-06",
"id": 40,
"note": null,
"schedule_id": 3,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-13",
"id": 41,
"note": null,
"schedule_id": 3,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-20",
"id": 42,
"note": null,
"schedule_id": 3,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
},
{
"date": "2030-01-27",
"id": 43,
"note": null,
"schedule_id": 3,
"source": "generated",
"team_id": null,
"updated_at": "<timestamp>"
}
],
"monthly_patterns": [],
"notification_logs": [
{
"body": "Equipa Norte propõe trocar o turno de Rotação Phase 4 de 5 de janeiro de 2030 pelo seu turno de 12 de janeiro de 2030.\n\nTeste de troca\n\nAbrir a escala: http://testserver/swaps",
"channel": "email",
"created_at": "<timestamp>",
"error": "SMTP_HOST não está configurado",
"event_type": "swap_requested",
"id": 1,
"recipient": "b@example.com",
"sent_at": null,
"status": "skipped",
"subject": "Troca de turnos: sáb 05 jan por sáb 12 jan – Rotação Phase 4",
"team_id": 3
},
{
"body": "Equipa Sul aceitou a troca de Rotação Phase 4 de 5 de janeiro de 2030 por 12 de janeiro de 2030, pedida por Equipa Norte. Aprove em http://testserver/swaps",
"channel": "email",
"created_at": "<timestamp>",
"error": "SMTP_HOST não está configurado",
"event_type": "swap_needs_approval",
"id": 2,
"recipient": "admin@example.com",
"sent_at": null,
"status": "skipped",
"subject": "Uma troca de turno precisa de aprovação",
"team_id": 1
},
{
"body": "Equipa Sul aceitou o seu pedido. Está agora à espera da aprovação da gestão.",
"channel": "email",
"created_at": "<timestamp>",
"error": "SMTP_HOST não está configurado",
"event_type": "swap_accepted",
"id": 3,
"recipient": "a@example.com",
"sent_at": null,
"status": "skipped",
"subject": "A sua troca de turno foi aceite",
"team_id": 2
},
{
"body": "Administrador de teste aprovou a troca de Rotação Phase 4: Equipa Sul fica com o turno de 5 de janeiro de 2030 e Equipa Norte com o de 12 de janeiro de 2030.",
"channel": "email",
"created_at": "<timestamp>",
"error": "SMTP_HOST não está configurado",
"event_type": "swap_approved",
"id": 4,
"recipient": "a@example.com",
"sent_at": null,
"status": "skipped",
"subject": "Troca de turno aprovada",
"team_id": 2
},
{
"body": "Administrador de teste aprovou a troca de Rotação Phase 4: Equipa Sul fica com o turno de 5 de janeiro de 2030 e Equipa Norte com o de 12 de janeiro de 2030.",
"channel": "email",
"created_at": "<timestamp>",
"error": "SMTP_HOST não está configurado",
"event_type": "swap_approved",
"id": 5,
"recipient": "b@example.com",
"sent_at": null,
"status": "skipped",
"subject": "Troca de turno aprovada",
"team_id": 3
},
{
"body": "Administrador de teste reverteu a troca de Rotação Phase 4. Equipa Norte volta a 5 de janeiro de 2030 e Equipa Sul volta a 12 de janeiro de 2030.\n\nVer a escala: http://testserver/",
"channel": "email",
"created_at": "<timestamp>",
"error": "SMTP_HOST não está configurado",
"event_type": "swap_reverted",
"id": 6,
"recipient": "a@example.com",
"sent_at": null,
"status": "skipped",
"subject": "Troca de turno revertida",
"team_id": 2
},
{
"body": "Administrador de teste reverteu a troca de Rotação Phase 4. Equipa Norte volta a 5 de janeiro de 2030 e Equipa Sul volta a 12 de janeiro de 2030.\n\nVer a escala: http://testserver/",
"channel": "email",
"created_at": "<timestamp>",
"error": "SMTP_HOST não está configurado",
"event_type": "swap_reverted",
"id": 7,
"recipient": "b@example.com",
"sent_at": null,
"status": "skipped",
"subject": "Troca de turno revertida",
"team_id": 3
},
{
"body": "Já não está atribuído ao turno de Rotação Phase 4 a 26 de janeiro de 2030.\n\nVer a escala: http://testserver/",
"channel": "email",
"created_at": "<timestamp>",
"error": "SMTP_HOST não está configurado",
"event_type": "assignment_changed",
"id": 8,
"recipient": "b@example.com",
"sent_at": null,
"status": "skipped",
"subject": "Escala atualizada: sáb 26 jan – Rotação Phase 4",
"team_id": 3
},
{
"body": "Está atribuído ao turno de Rotação Phase 4 a 26 de janeiro de 2030.\n\nVer a escala: http://testserver/",
"channel": "email",
"created_at": "<timestamp>",
"error": "SMTP_HOST não está configurado",
"event_type": "assignment_updated",
"id": 9,
"recipient": "b@example.com",
"sent_at": null,
"status": "skipped",
"subject": "Escala atualizada: sáb 26 jan – Rotação Phase 4",
"team_id": 3
}
],
"rotation_members": [
{
"id": 1,
"position": 1,
"schedule_id": 4,
"team_id": 2
},
{
"id": 2,
"position": 2,
"schedule_id": 4,
"team_id": 3
}
],
"schedules": [
{
"created_at": "<timestamp>",
"end_time": "00:00:00.000000",
"id": 1,
"is_active": 1,
"name": "Noite",
"requires_manager_approval": 1,
"rotation_anchor_date": null,
"schedule_type": "fixed",
"slug": "night",
"start_time": "20:30:00.000000",
"weekdays": "0,1,2,3,4,5,6"
},
{
"created_at": "<timestamp>",
"end_time": "16:00:00.000000",
"id": 2,
"is_active": 1,
"name": "Almoço - Sábados",
"requires_manager_approval": 1,
"rotation_anchor_date": "2024-01-06",
"schedule_type": "rotation",
"slug": "saturday-lunch",
"start_time": "13:00:00.000000",
"weekdays": "5"
},
{
"created_at": "<timestamp>",
"end_time": "16:00:00.000000",
"id": 3,
"is_active": 1,
"name": "Almoço - Domingo",
"requires_manager_approval": 1,
"rotation_anchor_date": "2024-01-07",
"schedule_type": "rotation",
"slug": "sunday-lunch",
"start_time": "13:00:00.000000",
"weekdays": "6"
},
{
"created_at": "<timestamp>",
"end_time": "14:00:00.000000",
"id": 4,
"is_active": 1,
"name": "Rotação Phase 4",
"requires_manager_approval": 1,
"rotation_anchor_date": "2030-01-05",
"schedule_type": "rotation",
"slug": "phase4-rotation",
"start_time": "12:00:00.000000",
"weekdays": "5"
}
],
"swap_requests": [
{
"accepted_team_id": 3,
"assignment_id": 1,
"created_at": "<timestamp>",
"id": 1,
"message": "Teste de troca",
"requester_id": 2,
"status": "reverted",
"target_assignment_id": 2,
"target_team_id": 3,
"updated_at": "<timestamp>"
}
],
"teams": [
{
"calendar_token": null,
"created_at": "<timestamp>",
"email": "admin@example.com",
"id": 1,
"is_active": 1,
"name": "Administrador de teste",
"notify_email": 1,
"phone": null,
"user_id": 1
},
{
"calendar_token": "phase4-feed-a",
"created_at": "<timestamp>",
"email": "a@example.com",
"id": 2,
"is_active": 1,
"name": "Equipa Norte",
"notify_email": 1,
"phone": null,
"user_id": 2
},
{
"calendar_token": "phase4-feed-b",
"created_at": "<timestamp>",
"email": "b@example.com",
"id": 3,
"is_active": 1,
"name": "Equipa Sul",
"notify_email": 1,
"phone": null,
"user_id": 3
},
{
"calendar_token": "phase4-feed-c",
"created_at": "<timestamp>",
"email": "c@example.com",
"id": 4,
"is_active": 1,
"name": "Equipa partilhada",
"notify_email": 1,
"phone": null,
"user_id": 2
}
],
"users": [
{
"created_at": "<timestamp>",
"id": 1,
"is_active": 1,
"is_admin": 1,
"name": "Administrador de teste",
"password_hash": "<password-hash>",
"username": "phase4-admin"
},
{
"created_at": "<timestamp>",
"id": 2,
"is_active": 1,
"is_admin": 0,
"name": "Membro a",
"password_hash": "<password-hash>",
"username": "member-a"
},
{
"created_at": "<timestamp>",
"id": 3,
"is_active": 1,
"is_admin": 0,
"name": "Membro b",
"password_hash": "<password-hash>",
"username": "member-b"
}
]
}
}
}
}