Day 25 was the Advent of Code tradition of a one-part problem, even though this had two parts. (And it wasn't until after I'd done this that I found the name for this kind of representation: balanced numbers, such as balanced ternary.)
The conversion from SNAFU numbers to decimal was just like any other base conversion: read a digit, multiply by the base, convert the rest.
readSnafu :: String -> Int
readSnafu cs = foldl' go 0 cs
where go acc c = acc * 5 + (snafuValue c)
snafuValue :: Char -> Int
snafuValue '2' = 2
snafuValue '1' = 1
snafuValue '0' = 0
snafuValue '-' = -1
snafuValue '=' = -2
The conversion from decimal to SNAFU took a bit more thinking. Eventually I worked out that, once you'd fixed the least significant few digits, those wouldn't change as more significant digits were added.
I took a long-winded approach of converting the number into a list of base-five elements (from least significant digit to most significant), then converting each digit in turn. If a digit was 3 or 4, I added a carry to the next digit.
showSnafu :: Int -> String
showSnafu = packSnafu . toBase5R
toBase5R :: Int -> [Int]
toBase5R 0 = []
toBase5R n = (r : (toBase5R k))
where (k, r) = n `divMod` 5
packSnafu :: [Int] -> String
packSnafu digits
| carry == 0 = shown
| otherwise = (snafuRep carry) : shown
where (carry, shown) = foldl' packSnafuDigit (0, "") digits
packSnafuDigit :: (Int, String) -> Int -> (Int, String)
packSnafuDigit (carry, acc) d
| d' <= 2 = (0, (snafuRep d') : acc)
| otherwise = (1, (snafuRep (d' - 5) : acc))
where d' = d + carry
snafuRep :: Int -> Char
snafuRep 2 = '2'
snafuRep 1 = '1'
snafuRep 0 = '0'
snafuRep -1 = '-'
snafuRep -2 = '='
snafuRep _ = error "Illegal number in show"
And that was it! All over for another year.

Code
You can get the code from my locally-hosted Git repo, or from Gitlab.